diff --git a/sprockets/cli.py b/sprockets/cli.py index c10eec1..1c7129c 100644 --- a/sprockets/cli.py +++ b/sprockets/cli.py @@ -32,7 +32,6 @@ else: import pkg_resources -from sprockets import daemon from sprockets import __version__ DESCRIPTION = 'Available sprockets application controllers' @@ -91,36 +90,14 @@ class CLI(object): # The list command prevents any other processing of args if self._args.list: self._print_installed_apps(self._args.controller) + sys.exit(0) - # Should be starting an application - else: + # If app is not specified at this point, raise an error + if not self._args.application: + sys.stderr.write('\nerror: application not specified\n\n') + self._arg_parser.print_help() + sys.exit(-1) - # If app is not specified at this point, raise an error - if not self._args.application: - sys.stderr.write('\nerror: application not specified\n\n') - self._arg_parser.print_help() - sys.exit(-1) - - # Fork into the background as a daemon if instructed to do so - if self._args.daemonize: - try: - with daemon.Daemon(self) as obj: - obj.start() - except (OSError, ValueError) as error: - sys.stderr.write('\nerror: could not start %s (%s)\n\n' % - (sys.argv[0], error)) - sys.exit(1) - - else: - # Start the application directly, no forking involved - self.start() - - def start(self): - """Invoked once the application is ready to be started. If the cli - args indicate that the application should be a daemon, it will already - be forked at this point. - - """ # If it's a registered app reference by name, get the module name app_module = self._get_application_module(self._args.controller, self._args.application) @@ -149,10 +126,6 @@ class CLI(object): action='store_true', help='List installed sprockets apps') - self._arg_parser.add_argument('-d', '--daemonize', - action='store_true', - help='Fork into a background process') - self._arg_parser.add_argument('-s', '--syslog', action='store_true', help='Log to syslog') diff --git a/sprockets/daemon.py b/sprockets/daemon.py deleted file mode 100644 index 95e5cfa..0000000 --- a/sprockets/daemon.py +++ /dev/null @@ -1,267 +0,0 @@ -""" -Unix daemonization support - -""" -import atexit -import datetime -import grp -import logging -import os -from os import path -import platform -import pwd -import re -import subprocess -import sys -import traceback -import warnings - - -# Ignore the DeprecationWarning caused by os.popen3 in Python 2.6 -warnings.filterwarnings("ignore", category=DeprecationWarning) - -LOGGER = logging.getLogger(__name__) - - -def operating_system(): - """Return a string identifying the operating system the application - is running on. - - :rtype: str - - """ - if platform.system() == 'Darwin': - return 'OS X Version %s' % platform.mac_ver()[0] - distribution = ' '.join(platform.linux_distribution()).strip() - os_platform = platform.platform(True, True) - if distribution: - os_platform += ' (%s)' % distribution - return os_platform - - -class Daemon(object): - """Daemonize the helper application, putting it in a forked background - process. - - """ - - def __init__(self, controller): - """Daemonize the controller, optionally passing in the user and group - to run as, a pid file, if core dumps should be prevented and a path to - write out exception logs to. - - :param controller: The controller to daaemonize & run - :type controller: helper.controller.Controller - - """ - # The logger is reset by the time it gets here, fix to avoid warnings - from sprockets import NullHandler - LOGGER.addHandler(NullHandler()) - - self.controller = controller - self.pidfile_path = self._get_pidfile_path() - - def __enter__(self): - """Context manager method to return the handle to this object. - - :rtype: Daemon - - """ - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """When leaving the context, examine why the context is leaving, if - it's an exception and log the error - - """ - if exc_type and not isinstance(exc_val, SystemExit): - LOGGER.error('Daemon context manager closed on exception: %r', - exc_type) - - def start(self): - """Daemonize if the process is not already running.""" - if self._is_already_running(): - LOGGER.error('Is already running') - sys.exit(1) - try: - self._daemonize() - self.controller.start() - except Exception as error: - sys.stderr.write('\nERROR: Startup of %s Failed\n.' % - sys.argv[0].split('/')[-1]) - exception_log = self._get_exception_log_path() - if exception_log: - with open(exception_log, 'a') as handle: - timestamp = datetime.datetime.now().isoformat() - handle.write('{:->80}\n'.format(' [START]')) - handle.write('%s Exception [%s]\n' % (sys.argv[0], - timestamp)) - handle.write('{:->80}\n'.format(' [INFO]')) - handle.write('Interpreter: %s\n' % sys.executable) - handle.write('CLI arguments: %s\n' % ' '.join(sys.argv)) - handle.write('Exception: %s\n' % error) - handle.write('Traceback:\n') - output = traceback.format_exception(*sys.exc_info()) - _dev_null = [(handle.write(line), - sys.stdout.write(line)) for line in output] - handle.write('{:->80}\n'.format(' [END]')) - handle.flush() - sys.stderr.write('\nException log: %s\n\n' % exception_log) - sys.exit(1) - - @property - def gid(self): - """Return the group id that the daemon will run with - - :rtype: int - - """ - return os.getgid() - - @property - def uid(self): - """Return the user id that the process will run as - - :rtype: int - - """ - return os.getuid() - - def _daemonize(self): - """Fork into a background process and setup the process, copied in part - from http://www.jejik.com/files/examples/daemon3x.py - - """ - LOGGER.info('Forking %s into the background', sys.argv[0]) - - # Write the pidfile if current uid != final uid - if os.getuid() != self.uid: - fd = open(self.pidfile_path, 'w') - os.fchmod(fd.fileno(), 0o644) - os.fchown(fd.fileno(), self.uid, self.gid) - fd.close() - - try: - pid = os.fork() - if pid > 0: - sys.exit(0) - except OSError as error: - raise OSError('Could not fork off parent: %s', error) - - # Decouple from parent environment - os.chdir('/') - os.setsid() - os.umask(0o022) - - # Fork again - try: - pid = os.fork() - if pid > 0: - sys.exit(0) - except OSError as error: - raise OSError('Could not fork child: %s', error) - - # redirect standard file descriptors - sys.stdout.flush() - sys.stderr.flush() - si = open(os.devnull, 'r') - so = open(os.devnull, 'a+') - se = open(os.devnull, 'a+') - os.dup2(si.fileno(), sys.stdin.fileno()) - os.dup2(so.fileno(), sys.stdout.fileno()) - os.dup2(se.fileno(), sys.stderr.fileno()) - - # Automatically call self._remove_pidfile when the app exits - atexit.register(self._remove_pidfile) - self._write_pidfile() - - @staticmethod - def _get_exception_log_path(): - """Return the normalized path for the connection log, raising an - exception if it can not written to. - - :return: str - - """ - app = sys.argv[0].split('/')[-1] - for exception_log in ['/var/log/%s.errors' % app, - '/var/tmp/%s.errors' % app, - '/tmp/%s.errors' % app]: - if os.access(path.dirname(exception_log), os.W_OK): - return exception_log - return None - - def _get_pidfile_path(self): - """Return the normalized path for the pidfile, raising an - exception if it can not written to. - - :return: str - :raises: ValueError - :raises: OSError - - """ - app = sys.argv[0].split('/')[-1] - for pidfile in ['%s/pids/%s.pid' % (os.getcwd(), app), - '/var/run/%s.pid' % app, - '/var/run/%s/%s.pid' % (app, app), - '/var/tmp/%s.pid' % app, - '/tmp/%s.pid' % app, - '%s.pid' % app]: - if os.access(path.dirname(pidfile), os.W_OK): - return pidfile - raise OSError('Could not find an appropriate place for a pid file') - - def _is_already_running(self): - """Check to see if the process is running, first looking for a pidfile, - then shelling out in either case, removing a pidfile if it exists but - the process is not running. - - """ - # Look for the pidfile, if exists determine if the process is alive - pidfile = self._get_pidfile_path() - if os.path.exists(pidfile): - pid = open(pidfile).read().strip() - try: - os.kill(int(pid), 0) - sys.stderr.write('Process already running as pid # %s\n' % pid) - return True - except OSError as error: - LOGGER.debug('Found pidfile, no process # %s', error) - os.unlink(pidfile) - - # Check the os for a process that is not this one that looks the same - pattern = ' '.join(sys.argv) - pattern = '[%s]%s' % (pattern[0], pattern[1:]) - try: - output = subprocess.check_output('ps a | grep "%s"' % pattern, - shell=True) - except AttributeError: - # Python 2.6 - stdin, stdout, stderr = os.popen3('ps a | grep "%s"' % pattern) - output = stdout.read() - except subprocess.CalledProcessError: - return False - pids = [int(pid) for pid in (re.findall(r'^([0-9]+)\s', - output.decode('latin-1')))] - if os.getpid() in pids: - pids.remove(os.getpid()) - if not pids: - return False - if len(pids) == 1: - pids = pids[0] - sys.stderr.write('Process already running as pid # %s\n' % pids) - return True - - def _remove_pidfile(self): - """Remove the pid file from the filesystem""" - LOGGER.debug('Removing pidfile: %s', self.pidfile_path) - try: - os.unlink(self.pidfile_path) - except OSError: - pass - - def _write_pidfile(self): - """Write the pid file out with the process number in the pid file""" - LOGGER.debug('Writing pidfile: %s', self.pidfile_path) - with open(self.pidfile_path, "w") as handle: - handle.write(str(os.getpid()))