<commit_before># -*- coding: utf-8 -*-
'''
The service module for FreeBSD

.. important::
    If you feel that Salt should be using this module to manage services on a
    minion, and it is using a different module (or gives an error similar to
    *'service.start' is not available*), see :ref:`here
    <module-provider-override>`.
'''
from __future__ import absolute_import, unicode_literals, print_function

# Import python libs
import logging
import os
import fnmatch
import re

# Import salt libs
import salt.utils.path
import salt.utils.decorators as decorators
import salt.utils.files
from salt.exceptions import CommandNotFoundError

__func_alias__ = {
    'reload_': 'reload'
}

log = logging.getLogger(__name__)

# Define the module's virtual name
__virtualname__ = 'service'

def __virtual__():
    '''
    Only work on FreeBSD
    '''
    # Disable on these platforms, specific service modules exist:
    if __grains__['os'] == 'FreeBSD':
        return __virtualname__
    return (False, 'The freebsdservice execution module cannot be loaded: only available on FreeBSD systems.')

@decorators.memoize
def _cmd(jail=None):
    '''
    Return full path to service command

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs
    '''
    service = salt.utils.path.which('service')
    if not service:
        raise CommandNotFoundError('\'service\' command not found')
    if jail:
        jexec = salt.utils.path.which('jexec')
        if not jexec:
            raise CommandNotFoundError('\'jexec\' command not found')
        service = '{0} {1} {2}'.format(jexec, jail, service)
    return service

def _get_jail_path(jail):
    '''
    .. versionadded:: 2016.3.4

    Return the jail's root directory (path) as shown in jls

    jail
        The jid or jail name
    '''
    jls = salt.utils.path.which('jls')
    if not jls:
        raise CommandNotFoundError('\'jls\' command not found')
    jails = __salt__['cmd.run_stdout']('{0} -n jid name path'.format(jls))
    for j in jails.splitlines():
        jid, jname, path = (x.split('=')[1].strip() for x in j.split())
        if jid == jail or jname == jail:
            return path.rstrip('/')
    # XΧΧ, TODO, not sure how to handle nonexistent jail
    return ''

def _get_rcscript(name, jail=None):
    '''
    Return full path to service rc script

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs
    '''
    cmd = '{0} -r'.format(_cmd(jail))
    prf = _get_jail_path(jail) if jail else ''
    for line in __salt__['cmd.run_stdout'](cmd, python_shell=False).splitlines():
        if line.endswith('{0}{1}'.format(os.path.sep, name)):
            return os.path.join(prf, line.lstrip(os.path.sep))
    return None

def _get_rcvar(name, jail=None):
    '''
    Return rcvar

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs
    '''
    if not available(name, jail):
        log.error('Service %s not found', name)
        return False

    cmd = '{0} {1} rcvar'.format(_cmd(jail), name)

    for line in __salt__['cmd.run_stdout'](cmd, python_shell=False).splitlines():
        if '_enable="' not in line:
            continue
        rcvar, _ = line.split('=', 1)
        return rcvar

    return None

def get_enabled(jail=None):
    '''
    Return what services are set to run on boot

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_enabled
    '''
    ret = []
    service = _cmd(jail)
    prf = _get_jail_path(jail) if jail else ''
    for svc in __salt__['cmd.run']('{0} -e'.format(service)).splitlines():
        ret.append(os.path.basename(svc))

    # This is workaround for bin/173454 bug
    for svc in get_all(jail):
        if svc in ret:
            continue
        if not os.path.exists('{0}/etc/rc.conf.d/{1}'.format(prf, svc)):
            continue
        if enabled(svc, jail=jail):
            ret.append(svc)

    return sorted(ret)

def get_disabled(jail=None):
    '''
    Return what services are available but not enabled to start at boot

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_disabled
    '''
    en_ = get_enabled(jail)
    all_ = get_all(jail)
    return sorted(set(all_) - set(en_))

def _switch(name,                   # pylint: disable=C0103
            on,                     # pylint: disable=C0103
            **kwargs):
    '''
    Switch on/off service start at boot.

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) and chroot keyword argument
    in kwargs. chroot should be used when jail's /etc is mounted read-only and
    should point to a root directory where jail's /etc is mounted read-write.
    '''
    jail = kwargs.get('jail', '')
    chroot = kwargs.get('chroot', '').rstrip('/')
    if not available(name, jail):
        return False

    rcvar = _get_rcvar(name, jail)
    if not rcvar:
        log.error('rcvar for service %s not found', name)
        return False

    if jail and not chroot:
        # prepend the jail's path in config paths when referring to a jail, when
        # chroot is not provided. chroot should be provided when the jail's /etc
        # is mounted read-only
        chroot = _get_jail_path(jail)

    config = kwargs.get('config',
                        __salt__['config.option']('service.config',
                                                  default='{0}/etc/rc.conf'.format(chroot)
                                                  )
                        )

    if not config:
        rcdir = '{0}/etc/rc.conf.d'.format(chroot)
        if not os.path.exists(rcdir) or not os.path.isdir(rcdir):
            log.error('%s not exists', rcdir)
            return False
        config = os.path.join(rcdir, rcvar.replace('_enable', ''))

    nlines = []
    edited = False

    if on:
        val = 'YES'
    else:
        val = 'NO'

    if os.path.exists(config):
        with salt.utils.files.fopen(config, 'r') as ifile:
            for line in ifile:
                line = salt.utils.stringutils.to_unicode(line).rstrip('\n')
                if not line.startswith('{0}='.format(rcvar)):
                    nlines.append(line)
                    continue
                rest = line[len(line.split()[0]):]  # keep comments etc
                nlines.append('{0}="{1}"{2}'.format(rcvar, val, rest))
                edited = True
    if not edited:
        # Ensure that the file ends in a \n
        if len(nlines) > 1 and nlines[-1][-1] != '\n':
            nlines[-1] = '{0}\n'.format(nlines[-1])
        nlines.append('{0}="{1}"\n'.format(rcvar, val))

    with salt.utils.files.fopen(config, 'w') as ofile:
        nlines = [salt.utils.stringutils.to_str(_l) for _l in nlines]
        ofile.writelines(nlines)

    return True

def enable(name, **kwargs):
    '''
    Enable the named service to start at boot

    name
        service name

    config : /etc/rc.conf
        Config file for managing service. If config value is
        empty string, then /etc/rc.conf.d/<service> used.
        See man rc.conf(5) for details.

        Also service.config variable can be used to change default.

    .. versionchanged:: 2016.3.4

    jail (optional keyword argument)
        the jail's id or name

    chroot (optional keyword argument)
        the jail's chroot, if the jail's /etc is not mounted read-write

    CLI Example:

    .. code-block:: bash

        salt '*' service.enable <service name>
    '''
    return _switch(name, True, **kwargs)

def disable(name, **kwargs):
    '''
    Disable the named service to start at boot

    Arguments the same as for enable()

    .. versionchanged:: 2016.3.4

    jail (optional keyword argument)
        the jail's id or name

    chroot (optional keyword argument)
        the jail's chroot, if the jail's /etc is not mounted read-write

    CLI Example:

    .. code-block:: bash

        salt '*' service.disable <service name>
    '''
    return _switch(name, False, **kwargs)

def enabled(name, **kwargs):
    '''
    Return True if the named service is enabled, false otherwise

    name
        Service name

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs

    CLI Example:

    .. code-block:: bash

        salt '*' service.enabled <service name>
    '''
    jail = kwargs.get('jail', '')
    if not available(name, jail):
        log.error('Service %s not found', name)
        return False

    cmd = '{0} {1} rcvar'.format(_cmd(jail), name)

    for line in __salt__['cmd.run_stdout'](cmd, python_shell=False).splitlines():
        if '_enable="' not in line:
            continue
        _, state, _ = line.split('"', 2)
        return state.lower() in ('yes', 'true', 'on', '1')

    # probably will never reached
    return False

def disabled(name, **kwargs):
    '''
    Return True if the named service is enabled, false otherwise

    CLI Example:

    .. code-block:: bash

        salt '*' service.disabled <service name>
    '''
    return not enabled(name, **kwargs)

def available(name, jail=None):
    '''
    Check that the given service is available.

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.available sshd
    '''
    return name in get_all(jail)

def missing(name, jail=None):
    '''
    The inverse of service.available.
    Returns ``True`` if the specified service is not available, otherwise returns
    ``False``.

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.missing sshd
    '''
    return name not in get_all(jail)

def get_all(jail=None):
    '''
    Return a list of all available services

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_all
    '''
    ret = []
    service = _cmd(jail)
    for srv in __salt__['cmd.run']('{0} -l'.format(service)).splitlines():
        if not srv.isupper():
            ret.append(srv)
    return sorted(ret)

def start(name, jail=None):
    '''
    Start the specified service

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.start <service name>
    '''
    cmd = '{0} {1} onestart'.format(_cmd(jail), name)
    return not __salt__['cmd.retcode'](cmd, python_shell=False)

def stop(name, jail=None):
    '''
    Stop the specified service

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.stop <service name>
    '''
    cmd = '{0} {1} onestop'.format(_cmd(jail), name)
    return not __salt__['cmd.retcode'](cmd, python_shell=False)

def restart(name, jail=None):
    '''
    Restart the named service

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.restart <service name>
    '''
    cmd = '{0} {1} onerestart'.format(_cmd(jail), name)
    return not __salt__['cmd.retcode'](cmd, python_shell=False)

def reload_(name, jail=None):
    '''
    Restart the named service

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.reload <service name>
    '''
    cmd = '{0} {1} onereload'.format(_cmd(jail), name)
    return not __salt__['cmd.retcode'](cmd, python_shell=False)

def status(name, sig=None, jail=None):
    '''
    Return the status for a service.
    If the name contains globbing, a dict mapping service name to True/False
    values is returned.

    .. versionchanged:: 2016.3.4

    .. versionchanged:: 2018.3.0
        The service name can now be a glob (e.g. ``salt*``)

    Args:
        name (str): The name of the service to check
        sig (str): Signature to use to find the service via ps

    Returns:
        bool: True if running, False otherwise
        dict: Maps service name to True if running, False otherwise

    CLI Example:

    .. code-block:: bash

        salt '*' service.status <service name> [service signature]
    '''
    if sig:
        return bool(__salt__['status.pid'](sig))

    contains_globbing = bool(re.search(r'\*|\?|\[.+\]', name))
    if contains_globbing:
        services = fnmatch.filter(get_all(), name)
    else:
        services = [name]
    results = {}
    for service in services:
        cmd = '{0} {1} onestatus'.format(_cmd(jail), service)
        results[service] = not __salt__['cmd.retcode'](cmd,
                                        python_shell=False,
                                        ignore_retcode=True)
    if contains_globbing:
        return results
    return results[name]
<commit_msg>Fix #47502: Remove an extraneous (accidentally introduced?) call to rstrip.<commit_after># -*- coding: utf-8 -*-
'''
The service module for FreeBSD

.. important::
    If you feel that Salt should be using this module to manage services on a
    minion, and it is using a different module (or gives an error similar to
    *'service.start' is not available*), see :ref:`here
    <module-provider-override>`.
'''
from __future__ import absolute_import, unicode_literals, print_function

# Import python libs
import logging
import os
import fnmatch
import re

# Import salt libs
import salt.utils.path
import salt.utils.decorators as decorators
import salt.utils.files
from salt.exceptions import CommandNotFoundError

__func_alias__ = {
    'reload_': 'reload'
}

log = logging.getLogger(__name__)

# Define the module's virtual name
__virtualname__ = 'service'

def __virtual__():
    '''
    Only work on FreeBSD
    '''
    # Disable on these platforms, specific service modules exist:
    if __grains__['os'] == 'FreeBSD':
        return __virtualname__
    return (False, 'The freebsdservice execution module cannot be loaded: only available on FreeBSD systems.')

@decorators.memoize
def _cmd(jail=None):
    '''
    Return full path to service command

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs
    '''
    service = salt.utils.path.which('service')
    if not service:
        raise CommandNotFoundError('\'service\' command not found')
    if jail:
        jexec = salt.utils.path.which('jexec')
        if not jexec:
            raise CommandNotFoundError('\'jexec\' command not found')
        service = '{0} {1} {2}'.format(jexec, jail, service)
    return service

def _get_jail_path(jail):
    '''
    .. versionadded:: 2016.3.4

    Return the jail's root directory (path) as shown in jls

    jail
        The jid or jail name
    '''
    jls = salt.utils.path.which('jls')
    if not jls:
        raise CommandNotFoundError('\'jls\' command not found')
    jails = __salt__['cmd.run_stdout']('{0} -n jid name path'.format(jls))
    for j in jails.splitlines():
        jid, jname, path = (x.split('=')[1].strip() for x in j.split())
        if jid == jail or jname == jail:
            return path.rstrip('/')
    # XΧΧ, TODO, not sure how to handle nonexistent jail
    return ''

def _get_rcscript(name, jail=None):
    '''
    Return full path to service rc script

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs
    '''
    cmd = '{0} -r'.format(_cmd(jail))
    prf = _get_jail_path(jail) if jail else ''
    for line in __salt__['cmd.run_stdout'](cmd, python_shell=False).splitlines():
        if line.endswith('{0}{1}'.format(os.path.sep, name)):
            return os.path.join(prf, line.lstrip(os.path.sep))
    return None

def _get_rcvar(name, jail=None):
    '''
    Return rcvar

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs
    '''
    if not available(name, jail):
        log.error('Service %s not found', name)
        return False

    cmd = '{0} {1} rcvar'.format(_cmd(jail), name)

    for line in __salt__['cmd.run_stdout'](cmd, python_shell=False).splitlines():
        if '_enable="' not in line:
            continue
        rcvar, _ = line.split('=', 1)
        return rcvar

    return None

def get_enabled(jail=None):
    '''
    Return what services are set to run on boot

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_enabled
    '''
    ret = []
    service = _cmd(jail)
    prf = _get_jail_path(jail) if jail else ''
    for svc in __salt__['cmd.run']('{0} -e'.format(service)).splitlines():
        ret.append(os.path.basename(svc))

    # This is workaround for bin/173454 bug
    for svc in get_all(jail):
        if svc in ret:
            continue
        if not os.path.exists('{0}/etc/rc.conf.d/{1}'.format(prf, svc)):
            continue
        if enabled(svc, jail=jail):
            ret.append(svc)

    return sorted(ret)

def get_disabled(jail=None):
    '''
    Return what services are available but not enabled to start at boot

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_disabled
    '''
    en_ = get_enabled(jail)
    all_ = get_all(jail)
    return sorted(set(all_) - set(en_))

def _switch(name,                   # pylint: disable=C0103
            on,                     # pylint: disable=C0103
            **kwargs):
    '''
    Switch on/off service start at boot.

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) and chroot keyword argument
    in kwargs. chroot should be used when jail's /etc is mounted read-only and
    should point to a root directory where jail's /etc is mounted read-write.
    '''
    jail = kwargs.get('jail', '')
    chroot = kwargs.get('chroot', '').rstrip('/')
    if not available(name, jail):
        return False

    rcvar = _get_rcvar(name, jail)
    if not rcvar:
        log.error('rcvar for service %s not found', name)
        return False

    if jail and not chroot:
        # prepend the jail's path in config paths when referring to a jail, when
        # chroot is not provided. chroot should be provided when the jail's /etc
        # is mounted read-only
        chroot = _get_jail_path(jail)

    config = kwargs.get('config',
                        __salt__['config.option']('service.config',
                                                  default='{0}/etc/rc.conf'.format(chroot)
                                                  )
                        )

    if not config:
        rcdir = '{0}/etc/rc.conf.d'.format(chroot)
        if not os.path.exists(rcdir) or not os.path.isdir(rcdir):
            log.error('%s not exists', rcdir)
            return False
        config = os.path.join(rcdir, rcvar.replace('_enable', ''))

    nlines = []
    edited = False

    if on:
        val = 'YES'
    else:
        val = 'NO'

    if os.path.exists(config):
        with salt.utils.files.fopen(config, 'r') as ifile:
            for line in ifile:
                line = salt.utils.stringutils.to_unicode(line)
                if not line.startswith('{0}='.format(rcvar)):
                    nlines.append(line)
                    continue
                rest = line[len(line.split()[0]):]  # keep comments etc
                nlines.append('{0}="{1}"{2}'.format(rcvar, val, rest))
                edited = True
    if not edited:
        # Ensure that the file ends in a \n
        if len(nlines) > 1 and nlines[-1][-1] != '\n':
            nlines[-1] = '{0}\n'.format(nlines[-1])
        nlines.append('{0}="{1}"\n'.format(rcvar, val))

    with salt.utils.files.fopen(config, 'w') as ofile:
        nlines = [salt.utils.stringutils.to_str(_l) for _l in nlines]
        ofile.writelines(nlines)

    return True

def enable(name, **kwargs):
    '''
    Enable the named service to start at boot

    name
        service name

    config : /etc/rc.conf
        Config file for managing service. If config value is
        empty string, then /etc/rc.conf.d/<service> used.
        See man rc.conf(5) for details.

        Also service.config variable can be used to change default.

    .. versionchanged:: 2016.3.4

    jail (optional keyword argument)
        the jail's id or name

    chroot (optional keyword argument)
        the jail's chroot, if the jail's /etc is not mounted read-write

    CLI Example:

    .. code-block:: bash

        salt '*' service.enable <service name>
    '''
    return _switch(name, True, **kwargs)

def disable(name, **kwargs):
    '''
    Disable the named service to start at boot

    Arguments the same as for enable()

    .. versionchanged:: 2016.3.4

    jail (optional keyword argument)
        the jail's id or name

    chroot (optional keyword argument)
        the jail's chroot, if the jail's /etc is not mounted read-write

    CLI Example:

    .. code-block:: bash

        salt '*' service.disable <service name>
    '''
    return _switch(name, False, **kwargs)

def enabled(name, **kwargs):
    '''
    Return True if the named service is enabled, false otherwise

    name
        Service name

    .. versionchanged:: 2016.3.4

    Support for jail (representing jid or jail name) keyword argument in kwargs

    CLI Example:

    .. code-block:: bash

        salt '*' service.enabled <service name>
    '''
    jail = kwargs.get('jail', '')
    if not available(name, jail):
        log.error('Service %s not found', name)
        return False

    cmd = '{0} {1} rcvar'.format(_cmd(jail), name)

    for line in __salt__['cmd.run_stdout'](cmd, python_shell=False).splitlines():
        if '_enable="' not in line:
            continue
        _, state, _ = line.split('"', 2)
        return state.lower() in ('yes', 'true', 'on', '1')

    # probably will never reached
    return False

def disabled(name, **kwargs):
    '''
    Return True if the named service is enabled, false otherwise

    CLI Example:

    .. code-block:: bash

        salt '*' service.disabled <service name>
    '''
    return not enabled(name, **kwargs)

def available(name, jail=None):
    '''
    Check that the given service is available.

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.available sshd
    '''
    return name in get_all(jail)

def missing(name, jail=None):
    '''
    The inverse of service.available.
    Returns ``True`` if the specified service is not available, otherwise returns
    ``False``.

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.missing sshd
    '''
    return name not in get_all(jail)

def get_all(jail=None):
    '''
    Return a list of all available services

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_all
    '''
    ret = []
    service = _cmd(jail)
    for srv in __salt__['cmd.run']('{0} -l'.format(service)).splitlines():
        if not srv.isupper():
            ret.append(srv)
    return sorted(ret)

def start(name, jail=None):
    '''
    Start the specified service

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.start <service name>
    '''
    cmd = '{0} {1} onestart'.format(_cmd(jail), name)
    return not __salt__['cmd.retcode'](cmd, python_shell=False)

def stop(name, jail=None):
    '''
    Stop the specified service

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.stop <service name>
    '''
    cmd = '{0} {1} onestop'.format(_cmd(jail), name)
    return not __salt__['cmd.retcode'](cmd, python_shell=False)

def restart(name, jail=None):
    '''
    Restart the named service

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.restart <service name>
    '''
    cmd = '{0} {1} onerestart'.format(_cmd(jail), name)
    return not __salt__['cmd.retcode'](cmd, python_shell=False)

def reload_(name, jail=None):
    '''
    Restart the named service

    .. versionchanged:: 2016.3.4

    jail: optional jid or jail name

    CLI Example:

    .. code-block:: bash

        salt '*' service.reload <service name>
    '''
    cmd = '{0} {1} onereload'.format(_cmd(jail), name)
    return not __salt__['cmd.retcode'](cmd, python_shell=False)

def status(name, sig=None, jail=None):
    '''
    Return the status for a service.
    If the name contains globbing, a dict mapping service name to True/False
    values is returned.

    .. versionchanged:: 2016.3.4

    .. versionchanged:: 2018.3.0
        The service name can now be a glob (e.g. ``salt*``)

    Args:
        name (str): The name of the service to check
        sig (str): Signature to use to find the service via ps

    Returns:
        bool: True if running, False otherwise
        dict: Maps service name to True if running, False otherwise

    CLI Example:

    .. code-block:: bash

        salt '*' service.status <service name> [service signature]
    '''
    if sig:
        return bool(__salt__['status.pid'](sig))

    contains_globbing = bool(re.search(r'\*|\?|\[.+\]', name))
    if contains_globbing:
        services = fnmatch.filter(get_all(), name)
    else:
        services = [name]
    results = {}
    for service in services:
        cmd = '{0} {1} onestatus'.format(_cmd(jail), service)
        results[service] = not __salt__['cmd.retcode'](cmd,
                                        python_shell=False,
                                        ignore_retcode=True)
    if contains_globbing:
        return results
    return results[name]
<|endoftext|>


<commit_before>"""Nodes manipulate incoming data and provide the outgoing data."""
from __future__ import print_function
from __future__ import absolute_import
from abc import ABCMeta, abstractmethod
try:
    from collections import OrderedDict
except ImportError:
    from ordereddict import OrderedDict
try:
    import importlib
except ImportError:
    pass
import inspect
import json
import uuid

from .plug import OutputPlug, InputPlug
from .log_observer import LogObserver
__all__ = ['INode']

def import_module(module):
    """Importing the given module works from python 2.6 to python 3."""
    try:
        module = importlib.import_module(module)
    except NameError:
        module = __import__(module, globals(), locals(), ['object'], -1)
    return module

class INode(object):
    """Holds input and output Plugs and a method for computing."""

    __metaclass__ = ABCMeta

    def __init__(self, name=None, identifier=None, engine="tk-shell"):
        """Initialize the input and output dictionaries and the name.

        Args:
            name (str): If not provided, the class name is used.
        """
        self.name = name if name is not None else self.__class__.__name__
        self.identifier = (identifier if identifier is not None
                           else '{0}-{1}'.format(self.name, uuid.uuid4()))
        self.inputs = dict()
        self.outputs = dict()
        self.engine = engine
        self.omit = False
        self.file_location = inspect.getfile(self.__class__)
        self.class_name = self.__class__.__name__

    def __unicode__(self):
        """Show all input and output Plugs."""
        return self.node_repr()

    def __str__(self):
        """Show all input and output Plugs."""
        return self.__unicode__().encode('utf-8').decode()

    @property
    def is_dirty(self):
        """Whether any of the input Plug data has changed and is dirty."""
        for input_ in self.inputs.values():
            if input_.is_dirty:
                return True
        return False

    @property
    def upstream_nodes(self):
        """Upper level Nodes feed inputs into this Node."""
        upstream_nodes = list()
        for input_ in self.inputs.values():
            upstream_nodes += [c.node for c in input_.connections]
        return list(set(upstream_nodes))

    @property
    def downstream_nodes(self):
        """Lower level nodes that this node feeds output to."""
        downstream_nodes = list()
        for output in self.outputs.values():
            downstream_nodes += [c.node for c in output.connections]
        return list(set(downstream_nodes))

    def evaluate(self):
        """Compute this Node, log it and clean the input Plugs."""
        if self.omit:
            LogObserver.push_message('Omitting {0} -> {1}'.format(
                self.file_location, self.class_name))
            return {}

        inputs = {}
        for name, plug in self.inputs.items():
            inputs[name] = plug.value

        LogObserver.push_message(
            'Evaluating {0} -> {1}.compute(**{2})'.format(
                self.file_location, self.class_name,
                json.dumps(self._sort_plugs(inputs), indent=2)))

        inputs = {}
        for name, plug in self.inputs.items():
            inputs[name] = plug.value

        # Compute and redirect the output to the output plugs
        outputs = self.compute(**inputs) or dict()
        for name, value in outputs.items():
            self.outputs[name].value = value

        # Set the inputs clean
        for input_ in self.inputs.values():
            input_.is_dirty = False

        LogObserver.push_message(
            'Evaluation result for {0} -> {1}: {2}'.format(
                self.file_location, self.class_name,
                json.dumps(self._sort_plugs(outputs), indent=2)))

        return outputs

    @abstractmethod
    def compute(self, *args, **kwargs):
        """Implement the data manipulation in the subclass.

        Return a dictionary with the outputs from this function.
        """

    def on_input_plug_set_dirty(self):
        """Propagate the dirty state to the connected downstream nodes."""
        for output_plug in self.outputs.values():
            for connected_plug in output_plug.connections:
                connected_plug.is_dirty = True

    def serialize(self):
        """Serialize the node to json."""
        inputs = OrderedDict()
        for plug in self.inputs.values():
            inputs[plug.name] = plug.serialize()
        outputs = OrderedDict()
        for plug in self.outputs.values():
            outputs[plug.name] = plug.serialize()
        return OrderedDict(
            module=self.__module__,
            cls=self.__class__.__name__,
            name=self.name,
            identifier=self.identifier,
            inputs=inputs,
            outputs=outputs,
            engine=self.engine)

    @staticmethod
    def deserialize(data):
        """De-serialize from the given json data."""
        node = getattr(import_module(data['module']), data['cls'], None)()
        node.post_deserialize(data)
        return node

    def post_deserialize(self, data):
        """Perform more data operations after initial serialization."""
        self.name = data['name']
        self.identifier = data['identifier']
        self.engine = data['engine']
        for name, input_ in data['inputs'].items():
            self.inputs[name].value = input_['value']

    def node_repr(self):
        """The node formated into a string looking like a node.

        +------------+
        | Node.Name  |
        |------------|
        o in         |
        |        out o
        +------------+
        """
        max_value_length = 10

        offset = ''
        if [i for i in self.inputs.values() if i.connections]:
            offset = ' ' * 3
        width = len(max(list(self.inputs) +
                        list(self.outputs) +
                        [self.name] +
                        list(plug.name + "".join([
                            s for i, s in enumerate(str(plug.value))
                            if i < max_value_length])
                            for plug in self.inputs.values()
                            if plug.value is not None) +
                        list(plug.name + "".join([
                            s for i, s in enumerate(str(plug.value))
                            if i < max_value_length])
                            for plug in self.outputs.values()
                            if plug.value is not None),
                        key=len)) + 5
        pretty = offset + '+' + '-' * width + '+'
        pretty += '\n{offset}|{name:^{width}}|'.format(
            offset=offset, name=' ' + self.name + ' ', width=width)
        pretty += '\n' + offset + '|' + '-' * width + '|'
        # Inputs
        for input_ in sorted(self.inputs.keys()):
            pretty += '\n'
            if self.inputs[input_].connections:
                pretty += '-->'
            else:
                pretty += offset

            value = ""
            if self.inputs[input_].value is not None:
                value = json.dumps(self.inputs[input_].value)
            plug = 'o {input_}<{value}>'.format(
                input_=input_,
                value="".join([s for i, s in enumerate(str(value))
                               if i < max_value_length]))
            pretty += '{plug:{width}}|'.format(plug=plug, width=width + 1)

        # Outputs
        for output in sorted(self.outputs.keys()):
            pretty += '\n{offset}|{output:>{width}} o'.format(
                offset=offset, output=output, width=width - 1)
            if self.outputs[output].connections:
                pretty += '---'

        pretty += '\n' + offset + '+' + '-' * width + '+'
        return pretty

    def list_repr(self):
        """List representation of the node showing inputs and their values."""
        pretty = []
        pretty.append(self.name)
        for name, plug in self.inputs.items():
            if plug.connections:
                pretty.append('  [i] {0} << {1}.{2}'.format(
                    name, plug.connections[0].node.name,
                    plug.connections[0].name))
            else:
                pretty.append('  [i] {0}: {1}'.format(name,
                                                      json.dumps(plug.value)))
        for name, plug in self.outputs.items():
            if plug.connections:
                pretty.append('  [o] {0} >> {1}.{2}'.format(
                    name, plug.connections[0].node.name,
                    plug.connections[0].name))
            else:
                pretty.append('  [o] {0}'.format(name))
        return '\n'.join(pretty)

    @staticmethod
    def _sort_plugs(plugs):
        sorted_plugs = OrderedDict()
        for i in sorted(plugs, key=lambda x: x.lower()):
            sorted_plugs[i] = plugs[i]
        return sorted_plugs

class FunctionNode(INode):
    """Wrap a function into a Node."""

    def __init__(self, func=None, outputs=None, name=None,
                 identifier=None, engine='tk-shell', **kwargs):
        """The data on the function is used to drive the Node.
        The function itself becomes the compute method.
        The function input args become the InputPlugs.
        Other function attributes, name, __doc__ also transfer to the Node.
        """
        super(FunctionNode, self).__init__(
            name or getattr(func, '__name__', None), identifier, engine)
        self._initialize(func, outputs or [], engine)
        for plug, value in kwargs.items():
            self.inputs[plug].value = value

        if func is not None:
            self.file_location = inspect.getfile(func)
            self.class_name = self.func.__name__

    def __call__(self, **kwargs):
        """Create and return an instance of the Node."""
        return FunctionNode(func=self.func,
                            outputs=[o for o in self.outputs],
                            engine=self.engine,
                            **kwargs)

    def compute(self, *args, **kwargs):
        """Call and return the wrapped function."""
        if self._use_self:
            return self.func(self, *args, **kwargs)
        else:
            return self.func(*args, **kwargs)

    def serialize(self):
        """Also serialize the location of the wrapped function."""
        data = super(FunctionNode, self).serialize()
        data['func'] = {
            'module': self.func.__module__,
            'name': self.func.__name__
        }
        return data

    def post_deserialize(self, data):
        """Apply the function back to the node."""
        self.name = data['name']
        self.identifier = data['identifier']
        node = getattr(import_module(data['func']['module']),
                       data['func']['name'], None)()
        self._initialize(node.func, data['outputs'].keys(), data['engine'])
        for name, input_ in data['inputs'].items():
            self.inputs[name].value = input_['value']

    def _initialize(self, func, outputs, engine):
        """Use the function and the list of outputs to setup the Node."""
        self.func = func
        self.__doc__ = func.__doc__
        self._use_self = False
        self.engine = engine
        if func is not None:
            for input_ in inspect.getargspec(func).args:
                if input_ != 'self':
                    InputPlug(input_, self)
                else:
                    self._use_self = True
        if outputs is not None:
            for output in outputs:
                OutputPlug(output, self)

def function_to_node(*args, **kwargs):
    """Wrap the given function into a Node."""
    def node(func):
        return FunctionNode(func, *args, **kwargs)
    return node
<commit_msg>Adding metadata concept to Nodes<commit_after>"""Nodes manipulate incoming data and provide the outgoing data."""
from __future__ import print_function
from __future__ import absolute_import
from abc import ABCMeta, abstractmethod
try:
    from collections import OrderedDict
except ImportError:
    from ordereddict import OrderedDict
try:
    import importlib
except ImportError:
    pass
import imp
import inspect
import json
import uuid

from .plug import OutputPlug, InputPlug
from .log_observer import LogObserver
__all__ = ['INode']

def import_module(module):
    """Importing the given module works from python 2.6 to python 3."""
    try:
        module = importlib.import_module(module)
    except NameError:
        module = __import__(module, globals(), locals(), ['object'], -1)
    return module

class INode(object):
    """Holds input and output Plugs and a method for computing."""

    __metaclass__ = ABCMeta

    def __init__(self, name=None, identifier=None, metadata=None):
        """Initialize the input and output dictionaries and the name.

        Args:
            name (str): If not provided, the class name is used.
        """
        self.name = name if name is not None else self.__class__.__name__
        self.identifier = (identifier if identifier is not None
                           else '{0}-{1}'.format(self.name, uuid.uuid4()))
        self.inputs = dict()
        self.outputs = dict()
        self.metadata = metadata or {}
        self.omit = False
        self.file_location = inspect.getfile(self.__class__)
        self.class_name = self.__class__.__name__

    def __unicode__(self):
        """Show all input and output Plugs."""
        return self.node_repr()

    def __str__(self):
        """Show all input and output Plugs."""
        return self.__unicode__().encode('utf-8').decode()

    @property
    def is_dirty(self):
        """Whether any of the input Plug data has changed and is dirty."""
        for input_ in self.inputs.values():
            if input_.is_dirty:
                return True
        return False

    @property
    def upstream_nodes(self):
        """Upper level Nodes feed inputs into this Node."""
        upstream_nodes = list()
        for input_ in self.inputs.values():
            upstream_nodes += [c.node for c in input_.connections]
        return list(set(upstream_nodes))

    @property
    def downstream_nodes(self):
        """Lower level nodes that this node feeds output to."""
        downstream_nodes = list()
        for output in self.outputs.values():
            downstream_nodes += [c.node for c in output.connections]
        return list(set(downstream_nodes))

    def evaluate(self):
        """Compute this Node, log it and clean the input Plugs."""
        if self.omit:
            LogObserver.push_message('Omitting {0} -> {1}'.format(
                self.file_location, self.class_name))
            return {}

        inputs = {}
        for name, plug in self.inputs.items():
            inputs[name] = plug.value

        LogObserver.push_message(
            'Evaluating {0} -> {1}.compute(**{2})'.format(
                self.file_location, self.class_name,
                json.dumps(self._sort_plugs(inputs), indent=2)))

        inputs = {}
        for name, plug in self.inputs.items():
            inputs[name] = plug.value

        # Compute and redirect the output to the output plugs
        outputs = self.compute(**inputs) or dict()
        for name, value in outputs.items():
            self.outputs[name].value = value

        # Set the inputs clean
        for input_ in self.inputs.values():
            input_.is_dirty = False

        LogObserver.push_message(
            'Evaluation result for {0} -> {1}: {2}'.format(
                self.file_location, self.class_name,
                json.dumps(self._sort_plugs(outputs), indent=2)))

        return outputs

    @abstractmethod
    def compute(self, *args, **kwargs):
        """Implement the data manipulation in the subclass.

        Return a dictionary with the outputs from this function.
        """

    def on_input_plug_set_dirty(self):
        """Propagate the dirty state to the connected downstream nodes."""
        for output_plug in self.outputs.values():
            for connected_plug in output_plug.connections:
                connected_plug.is_dirty = True

    def serialize(self):
        """Serialize the node to json."""
        inputs = OrderedDict()
        for plug in self.inputs.values():
            inputs[plug.name] = plug.serialize()
        outputs = OrderedDict()
        for plug in self.outputs.values():
            outputs[plug.name] = plug.serialize()
        return OrderedDict(
            file_location=self.file_location,
            module=self.__module__,
            cls=self.__class__.__name__,
            name=self.name,
            identifier=self.identifier,
            inputs=inputs,
            outputs=outputs,
            metadata=self.metadata)

    @staticmethod
    def deserialize(data):
        """De-serialize from the given json data."""
        try:
            node = getattr(import_module(data['module']), data['cls'])()
        except AttributeError:
            module = imp.load_source('module', data['file_location'])
            node = getattr(module, data['cls'])()
        node.post_deserialize(data)
        return node

    def post_deserialize(self, data):
        """Perform more data operations after initial serialization."""
        self.name = data['name']
        self.identifier = data['identifier']
        self.metadata = data['metadata']
        self.file_location = data['file_location']
        for name, input_ in data['inputs'].items():
            self.inputs[name].value = input_['value']

    def node_repr(self):
        """The node formated into a string looking like a node.

        +------------+
        | Node.Name  |
        |------------|
        o in         |
        |        out o
        +------------+
        """
        max_value_length = 10

        offset = ''
        if [i for i in self.inputs.values() if i.connections]:
            offset = ' ' * 3
        width = len(max(list(self.inputs) +
                        list(self.outputs) +
                        [self.name] +
                        list(plug.name + "".join([
                            s for i, s in enumerate(str(plug.value))
                            if i < max_value_length])
                            for plug in self.inputs.values()
                            if plug.value is not None) +
                        list(plug.name + "".join([
                            s for i, s in enumerate(str(plug.value))
                            if i < max_value_length])
                            for plug in self.outputs.values()
                            if plug.value is not None),
                        key=len)) + 5
        pretty = offset + '+' + '-' * width + '+'
        pretty += '\n{offset}|{name:^{width}}|'.format(
            offset=offset, name=' ' + self.name + ' ', width=width)
        pretty += '\n' + offset + '|' + '-' * width + '|'
        # Inputs
        for input_ in sorted(self.inputs.keys()):
            pretty += '\n'
            if self.inputs[input_].connections:
                pretty += '-->'
            else:
                pretty += offset

            value = ""
            if self.inputs[input_].value is not None:
                value = json.dumps(self.inputs[input_].value)
            plug = 'o {input_}<{value}>'.format(
                input_=input_,
                value="".join([s for i, s in enumerate(str(value))
                               if i < max_value_length]))
            pretty += '{plug:{width}}|'.format(plug=plug, width=width + 1)

        # Outputs
        for output in sorted(self.outputs.keys()):
            pretty += '\n{offset}|{output:>{width}} o'.format(
                offset=offset, output=output, width=width - 1)
            if self.outputs[output].connections:
                pretty += '---'

        pretty += '\n' + offset + '+' + '-' * width + '+'
        return pretty

    def list_repr(self):
        """List representation of the node showing inputs and their values."""
        pretty = []
        pretty.append(self.name)
        for name, plug in self.inputs.items():
            if plug.connections:
                pretty.append('  [i] {0} << {1}.{2}'.format(
                    name, plug.connections[0].node.name,
                    plug.connections[0].name))
            else:
                pretty.append('  [i] {0}: {1}'.format(name,
                                                      json.dumps(plug.value)))
        for name, plug in self.outputs.items():
            if plug.connections:
                pretty.append('  [o] {0} >> {1}.{2}'.format(
                    name, plug.connections[0].node.name,
                    plug.connections[0].name))
            else:
                pretty.append('  [o] {0}'.format(name))
        return '\n'.join(pretty)

    @staticmethod
    def _sort_plugs(plugs):
        sorted_plugs = OrderedDict()
        for i in sorted(plugs, key=lambda x: x.lower()):
            sorted_plugs[i] = plugs[i]
        return sorted_plugs

class FunctionNode(INode):
    """Wrap a function into a Node."""

    def __init__(self, func=None, outputs=None, name=None,
                 identifier=None, metadata=None, **kwargs):
        """The data on the function is used to drive the Node.
        The function itself becomes the compute method.
        The function input args become the InputPlugs.
        Other function attributes, name, __doc__ also transfer to the Node.
        """
        super(FunctionNode, self).__init__(
            name or getattr(func, '__name__', None), identifier, metadata)
        self._initialize(func, outputs or [], metadata)
        for plug, value in kwargs.items():
            self.inputs[plug].value = value

        if func is not None:
            self.file_location = inspect.getfile(func)
            self.class_name = self.func.__name__

    def __call__(self, **kwargs):
        """Create and return an instance of the Node."""
        return FunctionNode(func=self.func,
                            outputs=[o for o in self.outputs],
                            metadata=self.metadata,
                            **kwargs)

    def compute(self, *args, **kwargs):
        """Call and return the wrapped function."""
        if self._use_self:
            return self.func(self, *args, **kwargs)
        else:
            return self.func(*args, **kwargs)

    def serialize(self):
        """Also serialize the location of the wrapped function."""
        data = super(FunctionNode, self).serialize()
        data['func'] = {
            'module': self.func.__module__,
            'name': self.func.__name__
        }
        return data

    def post_deserialize(self, data):
        """Apply the function back to the node."""
        self.name = data['name']
        self.identifier = data['identifier']
        self.metadata = data['metadata']
        self.file_location = data['file_location']
        module = imp.load_source('module', data['file_location'])
        node = getattr(module, data['func']['name'])()
        self._initialize(node.func, data['outputs'].keys(), data['metadata'])
        for name, input_ in data['inputs'].items():
            self.inputs[name].value = input_['value']

    def _initialize(self, func, outputs, metadata):
        """Use the function and the list of outputs to setup the Node."""
        self.func = func
        self.__doc__ = func.__doc__
        self._use_self = False
        self.metadata = metadata or {}
        if func is not None:
            for input_ in inspect.getargspec(func).args:
                if input_ != 'self':
                    InputPlug(input_, self)
                else:
                    self._use_self = True
        if outputs is not None:
            for output in outputs:
                OutputPlug(output, self)

def function_to_node(*args, **kwargs):
    """Wrap the given function into a Node."""
    def node(func):
        return FunctionNode(func, *args, **kwargs)
    return node
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
'''
Module for running imgadm command on SmartOS
'''
from __future__ import absolute_import, unicode_literals, print_function

# Import Python libs
import logging

# Import Salt libs
import salt.utils.json
import salt.utils.path
import salt.utils.platform

log = logging.getLogger(__name__)

# Function aliases
__func_alias__ = {
    'list_installed': 'list',
    'update_installed': 'update',
    'import_image': 'import'
}

# Define the module's virtual name
__virtualname__ = 'imgadm'

def __virtual__():
    '''
    Provides imgadm only on SmartOS
    '''
    if salt.utils.platform.is_smartos_globalzone() and \
            salt.utils.path.which('imgadm'):
        return __virtualname__
    return (
        False,
        '{0} module can only be loaded on SmartOS compute nodes'.format(
            __virtualname__
        )
    )

def _exit_status(retcode, stderr=None):
    '''
    Translate exit status of imgadm
    '''
    ret = {0: 'Successful completion.',
           1: 'An error occurred.' if not stderr else stderr,
           2: 'Usage error.',
           3: 'Image not installed.'}[retcode]
    return ret

def _parse_image_meta(image=None, detail=False):
    ret = None

    if image and 'Error' in image:
        ret = image
    elif image:
        name = image['manifest']['name']
        version = image['manifest']['version']
        os = image['manifest']['os']
        description = image['manifest']['description']
        published = image['manifest']['published_at']
        source = image['source']
        if image['manifest']['name'] == 'docker-layer':
            # NOTE: skip docker-layer unless it has a docker:repo and docker:tag
            name = None
            docker_repo = None
            docker_tag = None
            for tag in image['manifest']['tags']:
                if tag.startswith('docker:tag:') and image['manifest']['tags'][tag]:
                    docker_tag = tag.split(':')[-1]
                elif tag == 'docker:repo':
                    docker_repo = image['manifest']['tags'][tag]

            if docker_repo and docker_tag:
                name = '{}:{}'.format(docker_repo, docker_tag)
                description = 'Docker image imported from {repo}:{tag} on {date}.'.format(
                    repo=docker_repo,
                    tag=docker_tag,
                    date=published,
                )

        if name and detail:
            ret = {
              'name': name,
              'version': version,
              'os': os,
              'description': description,
              'published': published,
              'source': source,
            }
        elif name:
            ret = '{name}@{version} [{published}]'.format(
                name=name,
                version=version,
                published=published,
            )

    return ret

def _split_docker_uuid(uuid):
    '''
    Split a smartos docker uuid into repo and tag
    '''
    if uuid:
        uuid = uuid.split(':')
        if len(uuid) == 2:
            tag = uuid[1]
            repo = uuid[0]
            if len(repo.split('/')) == 2:
                return repo, tag
    return None, None

def _is_uuid(uuid):
    '''
    Check if uuid is a valid smartos uuid

    Example: e69a0918-055d-11e5-8912-e3ceb6df4cf8
    '''
    if uuid and list((len(x) for x in uuid.split('-'))) == [8, 4, 4, 4, 12]:
        return True
    return False

def _is_docker_uuid(uuid):
    '''
    Check if uuid is a valid smartos docker uuid

    Example plexinc/pms-docker:plexpass
    '''
    repo, tag = _split_docker_uuid(uuid)
    return not (not repo and not tag)

def version():
    '''
    Return imgadm version

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.version
    '''
    ret = {}
    cmd = 'imgadm --version'
    res = __salt__['cmd.run'](cmd).splitlines()
    ret = res[0].split()
    return ret[-1]

def docker_to_uuid(uuid):
    '''
    Get the image uuid from an imported docker image

    .. versionadded:: 2019.2.0
    '''
    if _is_uuid(uuid):
        return uuid
    if _is_docker_uuid(uuid):
        images = list_installed(verbose=True)
        for image_uuid in images:
            if images[image_uuid]['name'] == uuid:
                return image_uuid
    return None

def update_installed(uuid=''):
    '''
    Gather info on unknown image(s) (locally installed)

    uuid : string
        optional uuid of image

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.update [uuid]
    '''
    cmd = 'imgadm update {0}'.format(uuid).rstrip()
    __salt__['cmd.run'](cmd)
    return {}

def avail(search=None, verbose=False):
    '''
    Return a list of available images

    search : string
        search keyword
    verbose : boolean (False)
        toggle verbose output

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.avail [percona]
        salt '*' imgadm.avail verbose=True
    '''
    ret = {}
    cmd = 'imgadm avail -j'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    for image in salt.utils.json.loads(res['stdout']):
        if image['manifest']['disabled'] or not image['manifest']['public']:
            continue
        if search and search not in image['manifest']['name']:
            # we skip if we are searching but don't have a match
            continue
        uuid = image['manifest']['uuid']
        data = _parse_image_meta(image, verbose)
        if data:
            ret[uuid] = data

    return ret

def list_installed(verbose=False):
    '''
    Return a list of installed images

    verbose : boolean (False)
        toggle verbose output

    .. versionchanged:: 2019.2.0

        Docker images are now also listed

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.list
        salt '*' imgadm.list docker=True
        salt '*' imgadm.list verbose=True
    '''
    ret = {}
    cmd = 'imgadm list -j'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    for image in salt.utils.json.loads(res['stdout']):
        uuid = image['manifest']['uuid']
        data = _parse_image_meta(image, verbose)
        if data:
            ret[uuid] = data

    return ret

def show(uuid):
    '''
    Show manifest of a given image

    uuid : string
        uuid of image

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.show e42f8c84-bbea-11e2-b920-078fab2aab1f
        salt '*' imgadm.show plexinc/pms-docker:plexpass
    '''
    ret = {}

    if _is_uuid(uuid) or _is_docker_uuid(uuid):
        cmd = 'imgadm show {0}'.format(uuid)
        res = __salt__['cmd.run_all'](cmd, python_shell=False)
        retcode = res['retcode']
        if retcode != 0:
            ret['Error'] = _exit_status(retcode, res['stderr'])
        else:
            ret = salt.utils.json.loads(res['stdout'])
    else:
        ret['Error'] = "{} is not a valid uuid.".format(uuid)

    return ret

def get(uuid):
    '''
    Return info on an installed image

    uuid : string
        uuid of image

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.get e42f8c84-bbea-11e2-b920-078fab2aab1f
        salt '*' imgadm.get plexinc/pms-docker:plexpass
    '''
    ret = {}

    if _is_docker_uuid(uuid):
        uuid = docker_to_uuid(uuid)

    if _is_uuid(uuid):
        cmd = 'imgadm get {0}'.format(uuid)
        res = __salt__['cmd.run_all'](cmd, python_shell=False)
        retcode = res['retcode']
        if retcode != 0:
            ret['Error'] = _exit_status(retcode, res['stderr'])
        else:
            ret = salt.utils.json.loads(res['stdout'])
    else:
        ret['Error'] = "{} is not a valid uuid.".format(uuid)

    return ret

def import_image(uuid, verbose=False):
    '''
    Import an image from the repository

    uuid : string
        uuid to import
    verbose : boolean (False)
        toggle verbose output

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.import e42f8c84-bbea-11e2-b920-078fab2aab1f [verbose=True]
    '''
    ret = {}
    cmd = 'imgadm import {0}'.format(uuid)
    res = __salt__['cmd.run_all'](cmd, python_shell=False)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    uuid = docker_to_uuid(uuid)
    data = _parse_image_meta(get(uuid), verbose)
    return {uuid: data}

def delete(uuid):
    '''
    Remove an installed image

    uuid : string
        Specifies uuid to import

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.delete e42f8c84-bbea-11e2-b920-078fab2aab1f
    '''
    ret = {}
    cmd = 'imgadm delete {0}'.format(uuid)
    res = __salt__['cmd.run_all'](cmd, python_shell=False)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret
    # output: Deleted image d5b3865c-0804-11e5-be21-dbc4ce844ddc
    result = []
    for image in res['stdout'].splitlines():
        image = [var for var in image.split(" ") if var]
        result.append(image[2])

    return result

def vacuum(verbose=False):
    '''
    Remove unused images

    verbose : boolean (False)
        toggle verbose output

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.vacuum [verbose=True]
    '''
    ret = {}
    cmd = 'imgadm vacuum -f'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret
    # output: Deleted image d5b3865c-0804-11e5-be21-dbc4ce844ddc (lx-centos-6@20150601)
    result = {}
    for image in res['stdout'].splitlines():
        image = [var for var in image.split(" ") if var]
        result[image[2]] = {
            'name': image[3][1:image[3].index('@')],
            'version': image[3][image[3].index('@')+1:-1]
        }
    if verbose:
        return result
    else:
        return list(result.keys())

def sources(verbose=False):
    '''
    Return a list of available sources

    verbose : boolean (False)
        toggle verbose output

    .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.sources
    '''
    ret = {}
    cmd = 'imgadm sources -j'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    for src in salt.utils.json.loads(res['stdout']):
        ret[src['url']] = src
        del src['url']

    if not verbose:
        ret = list(ret)

    return ret

def source_delete(source):
    '''
    Delete a source

    source : string
        source url to delete

    .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.source_delete https://updates.joyent.com
    '''
    ret = {}
    cmd = 'imgadm sources -d {0}'.format(source)
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode, res['stderr'])
        return ret

    return sources(False)

def source_add(source, source_type='imgapi'):
    '''
    Add a new source

    source : string
        source url to add
    source_trype : string (imgapi)
        source type, either imgapi or docker

    .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.source_add https://updates.joyent.com
        salt '*' imgadm.source_add https://docker.io docker
    '''
    ret = {}

    # NOTE: there are some undocumented deprecated source types
    #       so we just warn instead of error on those
    if source_type not in ['imgapi', 'docker']:
        log.warning('Possible unsupported imgage source type specified!')

    cmd = 'imgadm sources -a {0} -t {1}'.format(source, source_type)
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode, res['stderr'])
        return ret

    return sources(False)

# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
<commit_msg>imgadm module should deal with invalid images better<commit_after># -*- coding: utf-8 -*-
'''
Module for running imgadm command on SmartOS
'''
from __future__ import absolute_import, unicode_literals, print_function

# Import Python libs
import logging

# Import Salt libs
import salt.utils.json
import salt.utils.path
import salt.utils.platform

log = logging.getLogger(__name__)

# Function aliases
__func_alias__ = {
    'list_installed': 'list',
    'update_installed': 'update',
    'import_image': 'import'
}

# Define the module's virtual name
__virtualname__ = 'imgadm'

def __virtual__():
    '''
    Provides imgadm only on SmartOS
    '''
    if salt.utils.platform.is_smartos_globalzone() and \
            salt.utils.path.which('imgadm'):
        return __virtualname__
    return (
        False,
        '{0} module can only be loaded on SmartOS compute nodes'.format(
            __virtualname__
        )
    )

def _exit_status(retcode, stderr=None):
    '''
    Translate exit status of imgadm
    '''
    ret = {0: 'Successful completion.',
           1: 'An error occurred.' if not stderr else stderr,
           2: 'Usage error.',
           3: 'Image not installed.'}[retcode]
    return ret

def _parse_image_meta(image=None, detail=False):
    ret = None

    if image and 'Error' in image:
        ret = image
    elif image and 'manifest' in image:
        name = image['manifest']['name']
        version = image['manifest']['version']
        os = image['manifest']['os']
        description = image['manifest']['description']
        published = image['manifest']['published_at']
        source = image['source']
        if image['manifest']['name'] == 'docker-layer':
            # NOTE: skip docker-layer unless it has a docker:repo and docker:tag
            name = None
            docker_repo = None
            docker_tag = None
            for tag in image['manifest']['tags']:
                if tag.startswith('docker:tag:') and image['manifest']['tags'][tag]:
                    docker_tag = tag.split(':')[-1]
                elif tag == 'docker:repo':
                    docker_repo = image['manifest']['tags'][tag]

            if docker_repo and docker_tag:
                name = '{}:{}'.format(docker_repo, docker_tag)
                description = 'Docker image imported from {repo}:{tag} on {date}.'.format(
                    repo=docker_repo,
                    tag=docker_tag,
                    date=published,
                )

        if name and detail:
            ret = {
              'name': name,
              'version': version,
              'os': os,
              'description': description,
              'published': published,
              'source': source,
            }
        elif name:
            ret = '{name}@{version} [{published}]'.format(
                name=name,
                version=version,
                published=published,
            )

    return ret

def _split_docker_uuid(uuid):
    '''
    Split a smartos docker uuid into repo and tag
    '''
    if uuid:
        uuid = uuid.split(':')
        if len(uuid) == 2:
            tag = uuid[1]
            repo = uuid[0]
            if len(repo.split('/')) == 2:
                return repo, tag
    return None, None

def _is_uuid(uuid):
    '''
    Check if uuid is a valid smartos uuid

    Example: e69a0918-055d-11e5-8912-e3ceb6df4cf8
    '''
    if uuid and list((len(x) for x in uuid.split('-'))) == [8, 4, 4, 4, 12]:
        return True
    return False

def _is_docker_uuid(uuid):
    '''
    Check if uuid is a valid smartos docker uuid

    Example plexinc/pms-docker:plexpass
    '''
    repo, tag = _split_docker_uuid(uuid)
    return not (not repo and not tag)

def version():
    '''
    Return imgadm version

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.version
    '''
    ret = {}
    cmd = 'imgadm --version'
    res = __salt__['cmd.run'](cmd).splitlines()
    ret = res[0].split()
    return ret[-1]

def docker_to_uuid(uuid):
    '''
    Get the image uuid from an imported docker image

    .. versionadded:: 2019.2.0
    '''
    if _is_uuid(uuid):
        return uuid
    if _is_docker_uuid(uuid):
        images = list_installed(verbose=True)
        for image_uuid in images:
            if 'name' not in images[image_uuid]:
                continue
            if images[image_uuid]['name'] == uuid:
                return image_uuid
    return None

def update_installed(uuid=''):
    '''
    Gather info on unknown image(s) (locally installed)

    uuid : string
        optional uuid of image

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.update [uuid]
    '''
    cmd = 'imgadm update {0}'.format(uuid).rstrip()
    __salt__['cmd.run'](cmd)
    return {}

def avail(search=None, verbose=False):
    '''
    Return a list of available images

    search : string
        search keyword
    verbose : boolean (False)
        toggle verbose output

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.avail [percona]
        salt '*' imgadm.avail verbose=True
    '''
    ret = {}
    cmd = 'imgadm avail -j'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    for image in salt.utils.json.loads(res['stdout']):
        if image['manifest']['disabled'] or not image['manifest']['public']:
            continue
        if search and search not in image['manifest']['name']:
            # we skip if we are searching but don't have a match
            continue
        uuid = image['manifest']['uuid']
        data = _parse_image_meta(image, verbose)
        if data:
            ret[uuid] = data

    return ret

def list_installed(verbose=False):
    '''
    Return a list of installed images

    verbose : boolean (False)
        toggle verbose output

    .. versionchanged:: 2019.2.0

        Docker images are now also listed

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.list
        salt '*' imgadm.list docker=True
        salt '*' imgadm.list verbose=True
    '''
    ret = {}
    cmd = 'imgadm list -j'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    for image in salt.utils.json.loads(res['stdout']):
        uuid = image['manifest']['uuid']
        data = _parse_image_meta(image, verbose)
        if data:
            ret[uuid] = data

    return ret

def show(uuid):
    '''
    Show manifest of a given image

    uuid : string
        uuid of image

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.show e42f8c84-bbea-11e2-b920-078fab2aab1f
        salt '*' imgadm.show plexinc/pms-docker:plexpass
    '''
    ret = {}

    if _is_uuid(uuid) or _is_docker_uuid(uuid):
        cmd = 'imgadm show {0}'.format(uuid)
        res = __salt__['cmd.run_all'](cmd, python_shell=False)
        retcode = res['retcode']
        if retcode != 0:
            ret['Error'] = _exit_status(retcode, res['stderr'])
        else:
            ret = salt.utils.json.loads(res['stdout'])
    else:
        ret['Error'] = "{} is not a valid uuid.".format(uuid)

    return ret

def get(uuid):
    '''
    Return info on an installed image

    uuid : string
        uuid of image

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.get e42f8c84-bbea-11e2-b920-078fab2aab1f
        salt '*' imgadm.get plexinc/pms-docker:plexpass
    '''
    ret = {}

    if _is_docker_uuid(uuid):
        uuid = docker_to_uuid(uuid)

    if _is_uuid(uuid):
        cmd = 'imgadm get {0}'.format(uuid)
        res = __salt__['cmd.run_all'](cmd, python_shell=False)
        retcode = res['retcode']
        if retcode != 0:
            ret['Error'] = _exit_status(retcode, res['stderr'])
        else:
            ret = salt.utils.json.loads(res['stdout'])
    else:
        ret['Error'] = "{} is not a valid uuid.".format(uuid)

    return ret

def import_image(uuid, verbose=False):
    '''
    Import an image from the repository

    uuid : string
        uuid to import
    verbose : boolean (False)
        toggle verbose output

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.import e42f8c84-bbea-11e2-b920-078fab2aab1f [verbose=True]
    '''
    ret = {}
    cmd = 'imgadm import {0}'.format(uuid)
    res = __salt__['cmd.run_all'](cmd, python_shell=False)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    uuid = docker_to_uuid(uuid)
    data = _parse_image_meta(get(uuid), verbose)
    return {uuid: data}

def delete(uuid):
    '''
    Remove an installed image

    uuid : string
        Specifies uuid to import

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.delete e42f8c84-bbea-11e2-b920-078fab2aab1f
    '''
    ret = {}
    cmd = 'imgadm delete {0}'.format(uuid)
    res = __salt__['cmd.run_all'](cmd, python_shell=False)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret
    # output: Deleted image d5b3865c-0804-11e5-be21-dbc4ce844ddc
    result = []
    for image in res['stdout'].splitlines():
        image = [var for var in image.split(" ") if var]
        result.append(image[2])

    return result

def vacuum(verbose=False):
    '''
    Remove unused images

    verbose : boolean (False)
        toggle verbose output

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.vacuum [verbose=True]
    '''
    ret = {}
    cmd = 'imgadm vacuum -f'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret
    # output: Deleted image d5b3865c-0804-11e5-be21-dbc4ce844ddc (lx-centos-6@20150601)
    result = {}
    for image in res['stdout'].splitlines():
        image = [var for var in image.split(" ") if var]
        result[image[2]] = {
            'name': image[3][1:image[3].index('@')],
            'version': image[3][image[3].index('@')+1:-1]
        }
    if verbose:
        return result
    else:
        return list(result.keys())

def sources(verbose=False):
    '''
    Return a list of available sources

    verbose : boolean (False)
        toggle verbose output

    .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.sources
    '''
    ret = {}
    cmd = 'imgadm sources -j'
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode)
        return ret

    for src in salt.utils.json.loads(res['stdout']):
        ret[src['url']] = src
        del src['url']

    if not verbose:
        ret = list(ret)

    return ret

def source_delete(source):
    '''
    Delete a source

    source : string
        source url to delete

    .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.source_delete https://updates.joyent.com
    '''
    ret = {}
    cmd = 'imgadm sources -d {0}'.format(source)
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode, res['stderr'])
        return ret

    return sources(False)

def source_add(source, source_type='imgapi'):
    '''
    Add a new source

    source : string
        source url to add
    source_trype : string (imgapi)
        source type, either imgapi or docker

    .. versionadded:: 2019.2.0

    CLI Example:

    .. code-block:: bash

        salt '*' imgadm.source_add https://updates.joyent.com
        salt '*' imgadm.source_add https://docker.io docker
    '''
    ret = {}

    # NOTE: there are some undocumented deprecated source types
    #       so we just warn instead of error on those
    if source_type not in ['imgapi', 'docker']:
        log.warning('Possible unsupported imgage source type specified!')

    cmd = 'imgadm sources -a {0} -t {1}'.format(source, source_type)
    res = __salt__['cmd.run_all'](cmd)
    retcode = res['retcode']
    if retcode != 0:
        ret['Error'] = _exit_status(retcode, res['stderr'])
        return ret

    return sources(False)

# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
'''
Take data from salt and "return" it into a raw file containing the json, with
one line per event.

Add the following to the minion or master configuration file.

.. code-block:: yaml

    rawfile_json.filename: <path_to_output_file>

Default is ``/var/log/salt/events``.

Common use is to log all events on the master. This can generate a lot of
noise, so you may wish to configure batch processing and/or configure the
:conf_master:`event_return_whitelist` or :conf_master:`event_return_blacklist`
to restrict the events that are written.
'''

# Import python libs
from __future__ import absolute_import, print_function, with_statement, unicode_literals
import logging

import salt.returners
import salt.utils.files
import salt.utils.json

log = logging.getLogger(__name__)

# Define the module's virtual name
__virtualname__ = 'rawfile_json'

def __virtual__():
    return __virtualname__

def _get_options(ret):
    '''
    Returns options used for the rawfile_json returner.
    '''
    defaults = {'filename': '/var/log/salt/events'}
    attrs = {'filename': 'filename'}
    _options = salt.returners.get_returner_options(__virtualname__,
                                                   ret,
                                                   attrs,
                                                   __salt__=__salt__,
                                                   __opts__=__opts__,
                                                   defaults=defaults)

    return _options

def returner(ret):
    '''
    Write the return data to a file on the minion.
    '''
    opts = _get_options({})  # Pass in empty ret, since this is a list of events
    try:
        with salt.utils.files.flopen(opts['filename'], 'a') as logfile:
            salt.utils.json.dump(ret, logfile)
            logfile.write(str('\n'))  # future lint: disable=blacklisted-function
    except Exception:
        log.error('Could not write to rawdata_json file %s', opts['filename'])
        raise

def event_return(events):
    '''
    Write event data (return data and non-return data) to file on the master.
    '''
    if len(events) == 0:
        # events is an empty list.
        # Don't open the logfile in vain.
        return
    opts = _get_options({})  # Pass in empty ret, since this is a list of events
    try:
        with salt.utils.files.flopen(opts['filename'], 'a') as logfile:
            for event in events:
                salt.utils.json.dump(event, logfile)
                logfile.write(str('\n'))  # future lint: disable=blacklisted-function
    except Exception:
        log.error('Could not write to rawdata_json file %s', opts['filename'])
        raise
<commit_msg>Fix applying of attributes for returner rawfile_json<commit_after># -*- coding: utf-8 -*-
'''
Take data from salt and "return" it into a raw file containing the json, with
one line per event.

Add the following to the minion or master configuration file.

.. code-block:: yaml

    rawfile_json.filename: <path_to_output_file>

Default is ``/var/log/salt/events``.

Common use is to log all events on the master. This can generate a lot of
noise, so you may wish to configure batch processing and/or configure the
:conf_master:`event_return_whitelist` or :conf_master:`event_return_blacklist`
to restrict the events that are written.
'''

# Import python libs
from __future__ import absolute_import, print_function, with_statement, unicode_literals
import logging

import salt.returners
import salt.utils.files
import salt.utils.json

log = logging.getLogger(__name__)

# Define the module's virtual name
__virtualname__ = 'rawfile_json'

def __virtual__():
    return __virtualname__

def _get_options(ret):
    '''
    Returns options used for the rawfile_json returner.
    '''
    defaults = {'filename': '/var/log/salt/events'}
    attrs = {'filename': 'filename'}
    _options = salt.returners.get_returner_options(__virtualname__,
                                                   ret,
                                                   attrs,
                                                   __salt__=__salt__,
                                                   __opts__=__opts__,
                                                   defaults=defaults)

    return _options

def returner(ret):
    '''
    Write the return data to a file on the minion.
    '''
    opts = _get_options(ret)
    try:
        with salt.utils.files.flopen(opts['filename'], 'a') as logfile:
            salt.utils.json.dump(ret, logfile)
            logfile.write(str('\n'))  # future lint: disable=blacklisted-function
    except Exception:
        log.error('Could not write to rawdata_json file %s', opts['filename'])
        raise

def event_return(events):
    '''
    Write event data (return data and non-return data) to file on the master.
    '''
    if len(events) == 0:
        # events is an empty list.
        # Don't open the logfile in vain.
        return
    opts = _get_options({})  # Pass in empty ret, since this is a list of events
    try:
        with salt.utils.files.flopen(opts['filename'], 'a') as logfile:
            for event in events:
                salt.utils.json.dump(event, logfile)
                logfile.write(str('\n'))  # future lint: disable=blacklisted-function
    except Exception:
        log.error('Could not write to rawdata_json file %s', opts['filename'])
        raise
<|endoftext|>


<commit_before>#!/usr/bin/python

from flyrc import hostmask

class ProtocolViolation(Exception):
	def __init__(self, value, position=None):
		self.value = value
		self.position = position

	def __str__(self):
		return "<%s> %s" % (str(self.position), repr(self.value))

class InvalidArgumentOrder(ProtocolViolation):
	"""
	An argument containing spaces was found before
	the end of the argument list, or multiple arguments
	containing spaces were provided.
	"""
	pass

class EmptyArgument(ProtocolViolation):
	"""
	An argument was an empty string.
	"""
	pass

def irc_split(text):
	prefix = None
	command = None
	args = []
	if text:
		trailing = ''
		if text[0] == ':':
			prefix, text = text[1:].split(' ', 1)
		if text.find(' :') != -1:
			text, trailing = text.split(' :', 1)
		args = text.split(' ')
		if trailing:
			args.append(trailing)
		command = args.pop(0)
	return prefix, command, args

def irc_join(prefix, command, args):
	message = ''
	if prefix:
		message += ':' + str(prefix) + ' '
	message += str(command)
	for arg in args:
		if len(arg) == 0:
			continue # shouldn't happen.
		elif arg[0] == ':' or arg.find(' ') != -1:
			message += ' :' + arg
		else:
			message += ' ' + arg
	return message

class Step():
	NONE=0
	SEND=1
	RECV=2
	CONNECT=3

class Message(object):
	@classmethod
	def parse(cls, text):
		prefix, command, args = irc_split(text)
		source = None
		if prefix:
			source = hostmask.Hostmask.parse(prefix)
		return cls(source, command, args)

	def __init__(self, s, c, a):
		self._args = None
		self.source = s
		self.command = c
		self.args = a

	@property
	def args(self):
		return self._args

	@args.setter
	def args(self, newargs):
		for i, arg in enumerate(newargs):
			if len(arg) == 0:
				raise EmptyArgument(newargs, i)
			if i != len(newargs)-1 and (arg[0] == ':' or arg.find(' ') != -1):
				raise InvalidArgumentOrder(arg, i)
		self._args = newargs

	def render(self):
		return irc_join(self.source, self.command, self.args)

	def __repr__(self):
		return "<%s.%s(%s, %s, %s)>" % (type(self).__module__, type(self).__name__, repr(self.source), repr(self.command), repr(self.args))

class Error(object):
	def __init__(self, e, step=Step.NONE):
		self.e = e
		self.step = step

	def __repr__(self):
		return "<%s.%s(%s)>" % (type(self).__module__, type(self).__name__, self.e.__repr__)

def _gen_func(name, command):
	globals()[name] = lambda *args: Message(None, command, list(args))

_functions = [
	'notice',
	'ping',
	'pong',
	'join',
	'user',
	'nick',
	'whois',
	'names',
	'who',
	'whowas',
	'oper',
	'quit',
	'cap',
	'authenticate',
	'mode',
	'topic'
]

for name in _functions:
	_gen_func(name, name.upper())

_gen_func('msg', 'PRIVMSG')
<commit_msg>Comment out the check for empty arguments as MODE commands after a split can trigger this exception.<commit_after>#!/usr/bin/python

from flyrc import hostmask

class ProtocolViolation(Exception):
	def __init__(self, value, position=None):
		self.value = value
		self.position = position

	def __str__(self):
		return "<%s> %s" % (str(self.position), repr(self.value))

class InvalidArgumentOrder(ProtocolViolation):
	"""
	An argument containing spaces was found before
	the end of the argument list, or multiple arguments
	containing spaces were provided.
	"""
	pass

class EmptyArgument(ProtocolViolation):
	"""
	An argument was an empty string.
	"""
	pass

def irc_split(text):
	prefix = None
	command = None
	args = []
	if text:
		trailing = ''
		if text[0] == ':':
			prefix, text = text[1:].split(' ', 1)
		if text.find(' :') != -1:
			text, trailing = text.split(' :', 1)
		args = text.split(' ')
		if trailing:
			args.append(trailing)
		command = args.pop(0)
	return prefix, command, args

def irc_join(prefix, command, args):
	message = ''
	if prefix:
		message += ':' + str(prefix) + ' '
	message += str(command)
	for arg in args:
		if len(arg) == 0:
			continue # shouldn't happen.
		elif arg[0] == ':' or arg.find(' ') != -1:
			message += ' :' + arg
		else:
			message += ' ' + arg
	return message

class Step():
	NONE=0
	SEND=1
	RECV=2
	CONNECT=3

class Message(object):
	@classmethod
	def parse(cls, text):
		prefix, command, args = irc_split(text)
		source = None
		if prefix:
			source = hostmask.Hostmask.parse(prefix)
		return cls(source, command, args)

	def __init__(self, s, c, a):
		self._args = None
		self.source = s
		self.command = c
		self.args = a

	@property
	def args(self):
		return self._args

	@args.setter
	def args(self, newargs):
		for i, arg in enumerate(newargs):
			if len(arg) == 0:
				#raise EmptyArgument(newargs, i) # charybdis (and other hybrid derivatives) can hit this with a MODE after a split.
			if i != len(newargs)-1 and (arg[0] == ':' or arg.find(' ') != -1):
				raise InvalidArgumentOrder(arg, i)
		self._args = newargs

	def render(self):
		return irc_join(self.source, self.command, self.args)

	def __repr__(self):
		return "<%s.%s(%s, %s, %s)>" % (type(self).__module__, type(self).__name__, repr(self.source), repr(self.command), repr(self.args))

class Error(object):
	def __init__(self, e, step=Step.NONE):
		self.e = e
		self.step = step

	def __repr__(self):
		return "<%s.%s(%s)>" % (type(self).__module__, type(self).__name__, self.e.__repr__)

def _gen_func(name, command):
	globals()[name] = lambda *args: Message(None, command, list(args))

_functions = [
	'notice',
	'ping',
	'pong',
	'join',
	'user',
	'nick',
	'whois',
	'names',
	'who',
	'whowas',
	'oper',
	'quit',
	'cap',
	'authenticate',
	'mode',
	'topic'
]

for name in _functions:
	_gen_func(name, name.upper())

_gen_func('msg', 'PRIVMSG')
<|endoftext|>