From 16d3f0536e2be570c811443c85b44d2a09ee0c22 Mon Sep 17 00:00:00 2001 From: naarkhoo Date: Fri, 26 Jul 2013 01:21:47 -0400 Subject: [PATCH 1/2] init commit --- CONTRIBUTORS | 9 + LICENCE | 0 MANIFEST | 5 + README.rst | 104 ++++++++++++ TODO.rst | 5 + screenutils/__init__.py | 6 + screenutils/errors.py | 4 + screenutils/screen.py | 367 ++++++++++++++++++++++++++++++++++++++++ setup.py | 21 +++ 9 files changed, 521 insertions(+) create mode 100644 CONTRIBUTORS create mode 100644 LICENCE create mode 100644 MANIFEST create mode 100644 README.rst create mode 100644 TODO.rst create mode 100644 screenutils/__init__.py create mode 100644 screenutils/errors.py create mode 100644 screenutils/screen.py create mode 100644 setup.py diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..9ff8b5d --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,9 @@ +Contributors +============ + +screenutils is a project started and maintained by Christophe Narbonne. +The following people have contributed: + +- Alexis Métaireau +- Chritophe Narbonne +- Alireza Kashani diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..e69de29 diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..365fa14 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,5 @@ +README.rst +setup.py +screenutils/__init__.py +screenutils/errors.py +screenutils/screen.py diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..708f901 --- /dev/null +++ b/README.rst @@ -0,0 +1,104 @@ +screenutils +=========== + +screenutils is a set of classes that should help handling gnu-screen windows. + +Feel free to report any modification you made, the whole code source is +available under the terms of the GPLv2. + +Exemple usage +------------- + +Exemple in a python console:: + + >>> from screenutils import list_screens, Screen + >>> list_screens() + [] + >>> s= Screen("session1",True) + >>> # screen blink once + >>> # funky prompt should reduce logs lisibility so you should use sh or bash + >>> s.send_commands('bash') + >>> s.enable_logs() + >>> s.send_commands("df") ## basically this function didn't work and I added a similar function + >>> print next(s.logs) + df + Filesystem 1K-blocks Used Available Use% Mounted on + /dev/sda6 20161172 8084052 11052980 43% / + none 1505916 304 1505612 1% /dev + none 1512676 936 1511740 1% /dev/shm + none 1512676 380 1512296 1% /var/run + none 1512676 0 1512676 0% /var/lock + none 1512676 0 1512676 0% /lib/init/rw + none 20161172 8084052 11052980 43% /var/lib/ureadahead/debugfs + /dev/sda7 403567768 196284216 186783420 52% /home + popi@popi-laptop:~/Dev/github/screenutils$ + >>> s.disable_logs() + >>> s = None + >>> s = Screen("session1") + >>> s.exists + True + >>> s2 = Screen("session2") + >>> s2.exists + False + >>> s2.initialize() + >>> list_screens() + [, ] + >>> + + >>> conn = tunel('yourserver', username='yourusername',password='passx') + >>> print conn.execute_command_screen_ssh(command='python /myremotedir/myfolder/script.py', name='python') + >>> print conn.execute_command_screen_ssh(command='perl /myremotedir/myfolder/script.pl', name='perl') + >>> print conn.execute_command_screen_ssh(command='ls -d > /myremotedir/tmp.log', name='log') + >>> print conn.execute_command_screen_ssh(command='bash') + >>> print conn.list_screens_ssh + ['python', 'perl', 'log', 'bash'] + >>> print list_screens_ssh(conn) + >>> print conn.kill_screen_ssh('python') + >>> print conn.list_screens_ssh + ['perl', 'log' ,'bash'] + >>> conn.download('tmp.log','pathto/myremotedir/', 'pathto/localdir/') + >>> conn.upload('newfile.log','pathto/myremotedir/', 'pathto/localdir/') + >>> conn.execute('ls /myremotedir/') + +Installation +------------- + +You could install screenutils from github, by doing the following:: + + $ git clone http://github.com/Christophe31/screenutils.git + $ cd screenutils + $ python setup.py install + +Or by just using the packages publicated at pypi, for instance with pip:: + + $ pip install screenutils + +Features +--------- + + * screens listing + * screen session creation + * screen session closing + * screen code insertion + * screen monitoring/logging + * screen session sharing with unix users + - to allow this feature, you will **need** to change some unixs rigths: + + ``sudo chmod +s /usr/bin/screen`` + + ``sudo chmod 755 /var/run/screen`` + + * running command on a ssh tunel + * making screen session on ssh tunel + * terminating a screen session on ssh tunel + * screen listing on a ssh tunel + * download/upload on a ssh tunel + +Known issues +------------- + +This may not work properly with bpython. +running short commands such as "ls" can not be run on a screen session via ssh; since the terminal ends much faster than listing the result, however it works for heavier commands; for just generating a session, "bash" command is recommended + +Roadmap +-------- + + * multi windows screen support diff --git a/TODO.rst b/TODO.rst new file mode 100644 index 0000000..aceb8a2 --- /dev/null +++ b/TODO.rst @@ -0,0 +1,5 @@ +TODO list +========= + + * Add tests + diff --git a/screenutils/__init__.py b/screenutils/__init__.py new file mode 100644 index 0000000..4c6d342 --- /dev/null +++ b/screenutils/__init__.py @@ -0,0 +1,6 @@ +from screenutils.errors import ScreenNotFoundError +from screenutils.screen import list_screens, Screen + +__all__ = (list_screens, + Screen, + ScreenNotFoundError) diff --git a/screenutils/errors.py b/screenutils/errors.py new file mode 100644 index 0000000..bcd0653 --- /dev/null +++ b/screenutils/errors.py @@ -0,0 +1,4 @@ +"""Errors for the screenutils module""" + +class ScreenNotFoundError(Exception): + """raised when the screen does not exists""" diff --git a/screenutils/screen.py b/screenutils/screen.py new file mode 100644 index 0000000..059f769 --- /dev/null +++ b/screenutils/screen.py @@ -0,0 +1,367 @@ +# -*- coding:utf-8 -*- +# +# This program is free software. It comes without any warranty, to +# the extent permitted by applicable law. You can redistribute it +# and/or modify it under the terms of the GNU Public License 2 or upper. +# Please ask if you wish a more permissive license. + + +from screenutils.errors import ScreenNotFoundError +import os +import commands as cox + +try: + from commands import getoutput +except: + from subprocess import getoutput +from threading import Thread +from os import system +from os.path import isfile, getsize +from time import sleep + +def tailf(file_): + """Each value is content added to the log file since last value return""" + last_size = getsize(file_) + while True: + cur_size = getsize(file_) + if ( cur_size != last_size ): + f = open(file_, 'r') + f.seek(last_size if cur_size > last_size else 0) + text = f.read() + f.close() + last_size = cur_size + yield text + else: + yield "" + +def list_screens(): + """List all the existing screens and build a Screen instance for each + """ + return [ + Screen(".".join(l.split(".")[1:]).split("\t")[0]) + for l in getoutput("screen -ls | grep -P '\t'").split('\n') + if ".".join(l.split(".")[1:]).split("\t")[0] + ] + +def getfile(string, alternative): + """ return alternative if the string is empty + """ + if len(string) == 0: + return alternative + else: + return string + +class Screen(object): + """Represents a gnu-screen object:: + + >>> s=Screen("screenName", initialize=True) + >>> s.name + 'screenName' + >>> s.exists + True + >>> s.state + >>> s.send_commands("man -k keyboard") + >>> s.kill() + >>> s.exists + False + """ + + def __init__(self, name, initialize=False): + self.name = name + self.commanddict = dict() # for collecting the logs + self.flag = 'deactive' #do you need the status of a command ? + self._id = None + self._status = None + self.logs=None + if initialize: + self.initialize() + + @property + def id(self): + """return the identifier of the screen as string""" + if not self._id: + self._set_screen_infos() + return self._id + + @property + def status(self): + """return the status of the screen as string""" + self._set_screen_infos() + return self._status + + @property + def exists(self): + """Tell if the screen session exists or not.""" + # Parse the screen -ls call, to find if the screen exists or not. + # The screen -ls | grep name returns something like that: + # " 28062.G.Terminal (Detached)" + lines = getoutput("screen -ls | grep " + self.name).split('\n') + return self.name in [".".join(l.split(".")[1:]).split("\t")[0] + for l in lines] + + def enable_logs(self): + self.flag = 'active' + self._screen_commands("logfile " + self.name, "log on") + system('touch '+self.name) + self.logs=tailf(self.name) + next(self.logs) + + def disable_logs(self): + self._screen_commands("log off") + self.logs=None + self.flag = 'deactive' + + def initialize(self): + """initialize a screen, if does not exists yet""" + if not self.exists: + self._id=None + # Detach the screen once attached, on a new tread. + Thread(target=self._delayed_detach).start() + # support Unicode (-U), + # attach to a new/existing named screen (-R). + system('screen -UR ' + self.name) + + def interrupt(self): + """Insert CTRL+C in the screen session""" + self._screen_commands("eval \"stuff \\003\"") + + def kill(self): + """Kill the screen applications then close the screen""" + if self.flag == 'active': # remove the status files + if os.path.isfile(self.name+'.err'): + os.unlink(self.name+'.err') + if os.path.isfile(self.name+".log"): + os.unlink(self.name+'.err') + if os.path.isfile(self.name+".log"): + os.unlink(self.name+".log") + self._screen_commands('quit') + + def detach(self): + """detach the screen""" + self._check_exists() + system("screen -d " + self.name) + + + def send_commands_original(self, *commands): + """send commands to the active gnu-screen""" + self._check_exists() + + for command in commands: + self._screen_commands( 'stuff "' + command + '" ' , + 'eval "stuff \\015"' ) + + def send_commands(self, *commands, **status): + """send commands to the active gnu-screen""" + + status_show = status.get('status', False) + for command in commands: + if self.flag == 'active': + cmd = command + '> ' + str(self.name)+'.out 2> ' + str(self.name)+'.err' + else: + cmd = command + self._screen_commands( 'stuff "' + cmd + '" ' , + 'eval "stuff \\015"' ) + + if self.flag == 'active': + f = open(self.name+'.out', 'r') + textout = f.read() + f.close + + f = open(self.name+'.err', 'r') + texterr = f.read() + f.close + + if status_show == True: + print(textout) + if getsize(self.name + '.err') != 0: + print 'ERROR:\n' + # below is the log dictionary ! contains, command, stdout, stderr + self.commanddict[len(self.commanddict)+1] = [commands[0], getfile(textout, 'ERROR'), getfile(texterr, 'Done')] + + def add_user_access(self, unix_user_name): + """allow to share your session with an other unix user""" + self._screen_commands('multiuser on', 'acladd ' + unix_user_name) + + def report(self, n=1, entire=False): + """ return nth command status or write the entire log file + """ + L = range(1, len(self.commanddict)+1) + if entire==True: + f = open(str(self.name)+"_screen.log", "w") + for item in L: + f.writelines("# " + str(item) + "\n") + f.writelines(["%s\n" % item for item in self.commanddict.get(item)]) + f.writelines("--------------------\n\n") + f.close() + f = open(str(self.name)+"_screen.log", "r") + mylog = f.read() + print mylog + print 'find the log file at \n' + str(os.getcwd()) + "/" + str(self.name)+"_screen.log" + else: + f = open(str(self.name)+".log", "w") + for item in L[-n:]: + f.writelines("# " + str(item) + "\n") + f.writelines(["%s\n" % item for item in self.commanddict.get(item)]) + f.writelines("--------------------\n\n") + f.close() + f = open(str(self.name)+".log", "r") + mylog = f.read() + print mylog + + def cmdcounter(self): + """how many commands are executed in a given screen""" + print len(self.commanddict) + + def _screen_commands(self, *commands): + """allow to insert generic screen specific commands + a glossary of the existing screen command in `man screen`""" + self._check_exists() + for command in commands: + #~ print 'screen -x ' + self.name + ' -X ' + command + system('screen -x ' + self.name + ' -X ' + command) + #~ print(cox.getstatusoutput('screen -x ' + self.name + ' -X ' + command)) + sleep(0.02) + + def _check_exists(self, message="Error code: 404"): + """check whereas the screen exist. if not, raise an exception""" + if not self.exists: + raise ScreenNotFoundError(message) + + def _set_screen_infos(self): + """set the screen information related parameters""" + if self.exists: + infos = getoutput("screen -ls | grep %s" % self.name).split('\t')[1:] + self._id = infos[0].split('.')[0] + if len(infos)==3: + self._date = infos[1][1:-1] + self._status = infos[2][1:-1] + else: + self._status = infos[1][1:-1] + + def _delayed_detach(self): + sleep(0.5) + self.detach() + + def __repr__(self): + return "<%s '%s'>" % (self.__class__.__name__, self.name) + + +import paramiko +import os, shutil + + +class tunel(object): + def __init__(self, server, username, password='empty', key='empty', rsa =False): + self.server = server + self.username = username + self.password = password + self.rsa = rsa + self.key = key + + def execute(self,cmd): + self.ssh = paramiko.SSHClient() + self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + if self.password != 'empty' and self.rsa == True: + raise Exception("!! either password or rsa - not both") + if self.password == 'empty' and self.rsa == False and self.key == 'empty': + raise Exception("how should I access without rsa/password") + if self.password != 'empty': + self.ssh.connect(self.server, username=self.username, password=self.password) + if self.key != 'empty': + if os.path.isfile(self.key): + self.ssh.connect(self.server, username=self.username, key_filename=self.key) + else: + raise Exception('I cant see the key file at\n'+self.key) + if self.rsa == True: + rsafile = os.path.expanduser('~/.ssh/id_rsa') + print 'rsa file vojod dare' + if os.path.isfile(rsafile): + try: + mykey = paramiko.RSAKey.from_private_key_file(rsafile) + except: + raise Exception('If your rsa is encrypted - please make connection using the actual password') + self.ssh.connect(self.server, username=self.username, pkey=mykey) + else: + raise Exception('Cant see id_rsa at\n' + str(rsafile)) + + stdin, stdout, stderr = self.ssh.exec_command(cmd) + stderr.flush() + cmd_error = stderr.readlines() + cmd_out = stdout.readlines() + #~ cmd_in = stdin.readlines() + #~ if cmd_error: + #~ print 'Error :\n' + #~ print cmd_error[0] + #~ else: + #~ print 'the code is executed\n' + #~ print stdout.readlines() + self.ssh.close() + return cmd_out, cmd_error + + def upload(self, filename, remotepath, localpath): + ssh = paramiko.SSHClient() + #~ ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts"))) + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # take care of unknown hosts + + if self.password == 'empty' and self.rsa == False and self.key == 'empty': + raise Exception("how should I login ? \n provide me with password/key/rsa") + + elif self.password != 'empty' and self.rsa == False and self.key == 'empty': + ssh.connect(self.server, username=self.username, password=self.password) + + elif self.password == 'empty' and self.rsa == False and self.key != 'empty': + ssh.connect(self.server, username=self.username, key_filename=self.key) + + elif self.rsa == True: + rsafile = os.path.expanduser('~/.ssh/id_rsa') + if os.path.isfile(rsafile): + try: + mykey = paramiko.RSAKey.from_private_key_file(rsafile) + except: + raise Exception('If your rsa is encrypted - please make connection using the actual password') + self.ssh.connect(self.server, username=self.username, pkey=mykey) + else: + raise Exception('Cant see id_rsa at\n' + str(rsafile)) + + else: + raise Exception('unknown error in ssh communication') + + sftp = ssh.open_sftp() + try: + sftp.put(localpath+filename, remotepath+filename) + except OSError: + print 'Upload Error; possibilities\n file/localpath/remotepath does not exist\n' + sftp.close() + ssh.close() + tmp = tunel(self.server, self.username, self.password) + [cmdout,cmderror] = tmp.execute('ls '+ remotepath+filename) + if cmderror: + print 'ERORR in uploading : '+ str(cmderror[0]) + if cmdout: + print 'the file is there ! : ' + str(cmdout[0]) + + def kill_screen_ssh(conn, name): + '''kill a screen session by its name''' + cmd = 'screen -S '+name+' -X quit' + cmd_out, cmd_err = conn.execute(cmd) + return cmd_out, cmd_err + + def execute_command_screen_ssh(conn, command, name=''): + '''execute cmd in a server under a new screen session''' + if len(name) == 0: + name = command.split(' ')[0][0:5] + + screensonssh = list_screens_ssh(conn) + if name in screensonssh: + name = name+"_"+str(screensonssh.count(name)+1) + + cmd = 'screen -m -d -S '+name+' '+command + cmd_out, cmd_err = conn.execute(cmd) + return cmd_out, cmd_err + + +def list_screens_ssh(conn): + """List all the existing screens and build a Screen instance for each + """ + a, b = conn.execute("screen -ls | grep -P '\t'") + return [".".join(l.split(".")[1:]).split("\t")[0] for l in a] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6b84f4e --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +from distutils.core import setup + +setup( + name='screenutils', + version='0.0.1.5.3', + packages=['screenutils',], + license='GNU Public License >=2 (ask me if you want other)', + author="Christophe Narbonne", + author_email="@Christophe31", + url="http://github.com/Christophe31/screenutils", + description = + "lib for gnu-screen: creates/close/list/log sessions, injects commands...", + classifiers = [ + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + long_description=open('README.rst').read(), +) + From 9d44e62c8d57e73c75926891a5f571dc6bc2cc67 Mon Sep 17 00:00:00 2001 From: naarkhoo Date: Tue, 20 Aug 2013 19:00:13 -0400 Subject: [PATCH 2/2] test --- screenutils/screen.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/screenutils/screen.py b/screenutils/screen.py index 762d77b..012bc5b 100644 --- a/screenutils/screen.py +++ b/screenutils/screen.py @@ -5,6 +5,8 @@ # and/or modify it under the terms of the GNU Public License 2 or upper. # Please ask if you wish a more permissive license. +# here is a test +# think python chapter 5 from screenutils.errors import ScreenNotFoundError import os