#!/usr/bin/env python
# Copyright 1999-2017. Plesk International GmbH. All rights reserved.

""" phpinimng - Plesk backend utility for management of custom php.ini files
    and php-fpm pools.
"""

import os
import sys

from optparse import OptionParser

import plesk_log

log = plesk_log.getLogger(__name__)

class PhpIniMngApp:
    def __init__(self):
        self.options = None

    def parse_args(self, argv):
        """ Parse command line arguments, check their sanity and provide help. """
        usage = "usage: %prog [options] [php_ini_file]"
        parser = OptionParser(usage=usage)
        parser.add_option('-g', '--global-config',
                          help="Load custom global (server-wide) directives (takes lowest precedence, "
                               "this argument is used only for cgi handler type)")
        parser.add_option('-o', '--override',
                          help="Load custom directives (takes precedence over all others)")
        parser.add_option('-t', '--type', type='choice', choices=['cgi', 'fpm'], default='cgi',
                          help="Type of php handler - 'cgi' (default) or 'fpm'")
        parser.add_option('-v', '--virtual-host',
                          help="Virtual host to apply changes to")
        parser.add_option('-u', '--sysuser',
                          help="System user name for a given virtual host "
                               "(this argument is required only for fpm handler type)")

        parser.add_option('-r', '--remove', action='store_true',
                          help="Remove configuration file for specified virtual host and type")
        parser.add_option('-n', '--no-reload', action='store_true',
                          help="Don't reload php-fpm service")

        parser.add_option('--enable-service',
                          action='store_const', const='enable', dest='service_action',
                          help="Enable service (valid only for php-fpm)")
        parser.add_option('--disable-service',
                          action='store_const', const='disable', dest='service_action',
                          help="Disable service (valid only for php-fpm)")

        parser.add_option('--start', action='store_const', const='start', dest='service_action',
                          help="Start service (valid only for php-fpm)")
        parser.add_option('--stop', action='store_const', const='stop', dest='service_action',
                          help="Stop service (valid only for php-fpm)")
        parser.add_option('--restart', action='store_const', const='restart', dest='service_action',
                          help="Restart service (valid only for php-fpm)")
        parser.add_option('--status', action='store_const', const='status', dest='service_action',
                          help="Print service status (valid only for php-fpm)")

        parser.add_option('--service-name', action='store', dest='service_name',
                          help="Specify service name for fpm (valid only for php-fpm)")

        parser.add_option('--poold', action='store', dest="pool_d",
                          help="Specify pool.d path (valid only for php-fpm)")

        parser.add_option('--cgi-bin', action='store', dest="cgi_bin",
                          help="Specify path to php-fpm/cgi binary")

        (self.options, args) = parser.parse_args()

        log.debug("Options: %s", self.options)
        log.debug("Args: %s", args)
        if args:
            self._set_config_path(args[0])

        return self.options, args

    def _get_config_path(self):
        return getattr(self.options, 'config_path', None)

    def _set_config_path(self, path):
        setattr(self.options, 'config_path', path)

    def main(self, argv):
        """ Entire phpinimng utility, but in a callable form. """
        self.parse_args(argv[1:])

        """ FIXME: UI tries to interact with fpm without the --type option """
        if self.options.service_action:
            self.options.type = "fpm"

        module = __import__("php_" + self.options.type).DECLARES

        """ Check cmdline options """
        sanity_check = module['init']
        if sanity_check:
            sanity_check(self.options).check()

        if self.options.service_action:
            if not module['service']:
                raise Exception("Service '%' does not support '%s' action" % self.options.type, self.options.service_action)

            service = module['service'](self.options)
            # Service management
            if self.options.service_action == 'enable':
                service.enable()
            elif self.options.service_action == 'disable':
                service.disable()
            elif self.options.service_action == 'status':
                print(service.status() and "is running" or "is stopped")
            elif self.options.service_action == 'restart':
                service.smart_reload()
            else:
                service.action(self.options.service_action)
        else:
            config = module['config'](self.options)
            if not self._get_config_path():
                self._set_config_path(config.infer_config_path())
            config_path = self._get_config_path()
            log.debug("config_path: %s", config_path)

            needs_reload = True

            if self.options.remove:
                # Configs removal
                try:
                    os.unlink(config_path)
                    log.debug("Configuration file '%s' for '%s' removed successfully",
                              config_path, self.options.type)
                except Exception, ex:
                    log.warning("Removal of configuration file failed: %s", ex)
                    needs_reload = False
            else:
                # Configs writing
                os.umask(022)
                config.merge(self.options.override)

                backup_config = config_path + ".bkp"
                if os.path.exists(config_path):
                    os.rename(config_path, backup_config)

                with config.open(config_path) as conffile:
                    config.write(conffile)

                """ Syntax check config and roll them back if errors occured """
                if not config.check():
                    """ Unlink config to avoid problems with backup rename or absense """
                    os.unlink(config_path)
                    if os.path.exists(backup_config):
                        os.rename(backup_config, config_path)
                    raise Exception("Unable to update the config file for '%s' service due to syntax errors." % self.options.type)

                if os.path.exists(backup_config):
                    os.unlink(backup_config)

            if module['service'] and needs_reload and not self.options.no_reload:
                service = module['service'](self.options)
                service.smart_reload()

def main():
    """ phpinimng main entry point for command-line execution. """
    try:
        PhpIniMngApp().main(sys.argv)
    except SystemExit:
        raise
    except Exception, ex:
        sys.stderr.write('%s\n' % ex)
        log.debug('This exception happened at:', exc_info=sys.exc_info())
        sys.exit(1)

if __name__ == '__main__':
    main()

# vim: ts=4 sts=4 sw=4 et :
