%PDF- %PDF-
Direktori : /lib/python2.7/site-packages/salt/modules/ |
Current File : //lib/python2.7/site-packages/salt/modules/freebsdports.py |
# -*- coding: utf-8 -*- ''' Install software from the FreeBSD ``ports(7)`` system .. versionadded:: 2014.1.0 This module allows you to install ports using ``BATCH=yes`` to bypass configuration prompts. It is recommended to use the :mod:`ports state <salt.states.freebsdports>` to install ports, but it is also possible to use this module exclusively from the command line. .. code-block:: bash salt minion-id ports.config security/nmap IPV6=off salt minion-id ports.install security/nmap ''' from __future__ import absolute_import, unicode_literals, print_function # Import python libs import fnmatch import os import re import logging # Import salt libs import salt.utils.data import salt.utils.files import salt.utils.path from salt.ext.six import string_types from salt.exceptions import SaltInvocationError, CommandExecutionError from salt.ext import six log = logging.getLogger(__name__) # Define the module's virtual name __virtualname__ = 'ports' def __virtual__(): ''' Only runs on FreeBSD systems ''' if __grains__['os'] == 'FreeBSD': return __virtualname__ return (False, 'The freebsdports execution module cannot be loaded: ' 'only available on FreeBSD systems.') def _portsnap(): ''' Return 'portsnap --interactive' for FreeBSD 10, otherwise 'portsnap' ''' ret = ['portsnap'] if float(__grains__['osrelease']) >= 10: ret.append('--interactive') return ret def _check_portname(name): ''' Check if portname is valid and whether or not the directory exists in the ports tree. ''' if not isinstance(name, string_types) or '/' not in name: raise SaltInvocationError( 'Invalid port name \'{0}\' (category required)'.format(name) ) path = os.path.join('/usr/ports', name) if not os.path.isdir(path): raise SaltInvocationError('Path \'{0}\' does not exist'.format(path)) return path def _options_dir(name): ''' Retrieve the path to the dir containing OPTIONS file for a given port ''' _check_portname(name) _root = '/var/db/ports' # New path: /var/db/ports/category_portname new_dir = os.path.join(_root, name.replace('/', '_')) # Old path: /var/db/ports/portname old_dir = os.path.join(_root, name.split('/')[-1]) if os.path.isdir(old_dir): return old_dir return new_dir def _options_file_exists(name): ''' Returns True/False based on whether or not the options file for the specified port exists. ''' return os.path.isfile(os.path.join(_options_dir(name), 'options')) def _write_options(name, configuration): ''' Writes a new OPTIONS file ''' _check_portname(name) pkg = next(iter(configuration)) conf_ptr = configuration[pkg] dirname = _options_dir(name) if not os.path.isdir(dirname): try: os.makedirs(dirname) except OSError as exc: raise CommandExecutionError( 'Unable to make {0}: {1}'.format(dirname, exc) ) with salt.utils.files.fopen(os.path.join(dirname, 'options'), 'w') as fp_: sorted_options = list(conf_ptr) sorted_options.sort() fp_.write(salt.utils.stringutils.to_str( '# This file was auto-generated by Salt (http://saltstack.com)\n' '# Options for {0}\n' '_OPTIONS_READ={0}\n' '_FILE_COMPLETE_OPTIONS_LIST={1}\n' .format(pkg, ' '.join(sorted_options)) )) opt_tmpl = 'OPTIONS_FILE_{0}SET+={1}\n' for opt in sorted_options: fp_.write(salt.utils.stringutils.to_str( opt_tmpl.format( '' if conf_ptr[opt] == 'on' else 'UN', opt ) )) def _normalize(val): ''' Fix Salt's yaml-ification of on/off, and otherwise normalize the on/off values to be used in writing the options file ''' if isinstance(val, bool): return 'on' if val else 'off' return six.text_type(val).lower() def install(name, clean=True): ''' Install a port from the ports tree. Installs using ``BATCH=yes`` for non-interactive building. To set config options for a given port, use :mod:`ports.config <salt.modules.freebsdports.config>`. clean : True If ``True``, cleans after installation. Equivalent to running ``make install clean BATCH=yes``. .. note:: It may be helpful to run this function using the ``-t`` option to set a higher timeout, since compiling a port may cause the Salt command to exceed the default timeout. CLI Example: .. code-block:: bash salt -t 1200 '*' ports.install security/nmap ''' portpath = _check_portname(name) old = __salt__['pkg.list_pkgs']() if old.get(name.rsplit('/')[-1]): deinstall(name) cmd = ['make', 'install'] if clean: cmd.append('clean') cmd.append('BATCH=yes') result = __salt__['cmd.run_all']( cmd, cwd=portpath, reset_system_locale=False, python_shell=False ) if result['retcode'] != 0: __context__['ports.install_error'] = result['stderr'] __context__.pop('pkg.list_pkgs', None) new = __salt__['pkg.list_pkgs']() ret = salt.utils.data.compare_dicts(old, new) if not ret and result['retcode'] == 0: # No change in package list, but the make install was successful. # Assume that the installation was a recompile with new options, and # set return dict so that changes are detected by the ports.installed # state. ret = {name: {'old': old.get(name, ''), 'new': new.get(name, '')}} return ret def deinstall(name): ''' De-install a port. CLI Example: .. code-block:: bash salt '*' ports.deinstall security/nmap ''' portpath = _check_portname(name) old = __salt__['pkg.list_pkgs']() result = __salt__['cmd.run_all']( ['make', 'deinstall', 'BATCH=yes'], cwd=portpath, python_shell=False ) __context__.pop('pkg.list_pkgs', None) new = __salt__['pkg.list_pkgs']() return salt.utils.data.compare_dicts(old, new) def rmconfig(name): ''' Clear the cached options for the specified port; run a ``make rmconfig`` name The name of the port to clear CLI Example: .. code-block:: bash salt '*' ports.rmconfig security/nmap ''' portpath = _check_portname(name) return __salt__['cmd.run']( ['make', 'rmconfig'], cwd=portpath, python_shell=False ) def showconfig(name, default=False, dict_return=False): ''' Show the configuration options for a given port. default : False Show the default options for a port (not necessarily the same as the current configuration) dict_return : False Instead of returning the output of ``make showconfig``, return the data in an dictionary CLI Example: .. code-block:: bash salt '*' ports.showconfig security/nmap salt '*' ports.showconfig security/nmap default=True ''' portpath = _check_portname(name) if default and _options_file_exists(name): saved_config = showconfig(name, default=False, dict_return=True) rmconfig(name) if _options_file_exists(name): raise CommandExecutionError('Unable to get default configuration') default_config = showconfig(name, default=False, dict_return=dict_return) _write_options(name, saved_config) return default_config try: result = __salt__['cmd.run_all']( ['make', 'showconfig'], cwd=portpath, python_shell=False ) output = result['stdout'].splitlines() if result['retcode'] != 0: error = result['stderr'] else: error = '' except TypeError: error = result if error: msg = ('Error running \'make showconfig\' for {0}: {1}' .format(name, error)) log.error(msg) raise SaltInvocationError(msg) if not dict_return: return '\n'.join(output) if (not output) or ('configuration options' not in output[0]): return {} try: pkg = output[0].split()[-1].rstrip(':') except (IndexError, AttributeError, TypeError) as exc: log.error( 'Unable to get pkg-version string: {0}'.format(exc) ) return {} ret = {pkg: {}} output = output[1:] for line in output: try: opt, val, desc = re.match( r'\s+([^=]+)=(off|on): (.+)', line ).groups() except AttributeError: continue ret[pkg][opt] = val if not ret[pkg]: return {} return ret def config(name, reset=False, **kwargs): ''' Modify configuration options for a given port. Multiple options can be specified. To see the available options for a port, use :mod:`ports.showconfig <salt.modules.freebsdports.showconfig>`. name The port name, in ``category/name`` format reset : False If ``True``, runs a ``make rmconfig`` for the port, clearing its configuration before setting the desired options CLI Examples: .. code-block:: bash salt '*' ports.config security/nmap IPV6=off ''' portpath = _check_portname(name) if reset: rmconfig(name) configuration = showconfig(name, dict_return=True) if not configuration: raise CommandExecutionError( 'Unable to get port configuration for \'{0}\''.format(name) ) # Get top-level key for later reference pkg = next(iter(configuration)) conf_ptr = configuration[pkg] opts = dict( (six.text_type(x), _normalize(kwargs[x])) for x in kwargs if not x.startswith('_') ) bad_opts = [x for x in opts if x not in conf_ptr] if bad_opts: raise SaltInvocationError( 'The following opts are not valid for port {0}: {1}' .format(name, ', '.join(bad_opts)) ) bad_vals = [ '{0}={1}'.format(x, y) for x, y in six.iteritems(opts) if y not in ('on', 'off') ] if bad_vals: raise SaltInvocationError( 'The following key/value pairs are invalid: {0}' .format(', '.join(bad_vals)) ) conf_ptr.update(opts) _write_options(name, configuration) new_config = showconfig(name, dict_return=True) try: new_config = new_config[next(iter(new_config))] except (StopIteration, TypeError): return False return all(conf_ptr[x] == new_config.get(x) for x in conf_ptr) def update(extract=False): ''' Update the ports tree extract : False If ``True``, runs a ``portsnap extract`` after fetching, should be used for first-time installation of the ports tree. CLI Example: .. code-block:: bash salt '*' ports.update ''' result = __salt__['cmd.run_all']( _portsnap() + ['fetch'], python_shell=False ) if not result['retcode'] == 0: raise CommandExecutionError( 'Unable to fetch ports snapshot: {0}'.format(result['stderr']) ) ret = [] try: patch_count = re.search( r'Fetching (\d+) patches', result['stdout'] ).group(1) except AttributeError: patch_count = 0 try: new_port_count = re.search( r'Fetching (\d+) new ports or files', result['stdout'] ).group(1) except AttributeError: new_port_count = 0 ret.append('Applied {0} new patches'.format(patch_count)) ret.append('Fetched {0} new ports or files'.format(new_port_count)) if extract: result = __salt__['cmd.run_all']( _portsnap() + ['extract'], python_shell=False ) if not result['retcode'] == 0: raise CommandExecutionError( 'Unable to extract ports snapshot {0}'.format(result['stderr']) ) result = __salt__['cmd.run_all']( _portsnap() + ['update'], python_shell=False ) if not result['retcode'] == 0: raise CommandExecutionError( 'Unable to apply ports snapshot: {0}'.format(result['stderr']) ) __context__.pop('ports.list_all', None) return '\n'.join(ret) def list_all(): ''' Lists all ports available. CLI Example: .. code-block:: bash salt '*' ports.list_all .. warning:: Takes a while to run, and returns a **LOT** of output ''' if 'ports.list_all' not in __context__: __context__['ports.list_all'] = [] for path, dirs, files in salt.utils.path.os_walk('/usr/ports'): stripped = path[len('/usr/ports'):] if stripped.count('/') != 2 or stripped.endswith('/CVS'): continue __context__['ports.list_all'].append(stripped[1:]) return __context__['ports.list_all'] def search(name): ''' Search for matches in the ports tree. Globs are supported, and the category is optional CLI Examples: .. code-block:: bash salt '*' ports.search 'security/*' salt '*' ports.search 'security/n*' salt '*' ports.search nmap .. warning:: Takes a while to run ''' name = six.text_type(name) all_ports = list_all() if '/' in name: if name.count('/') > 1: raise SaltInvocationError( 'Invalid search string \'{0}\'. Port names cannot have more ' 'than one slash' ) else: return fnmatch.filter(all_ports, name) else: ret = [] for port in all_ports: if fnmatch.fnmatch(port.rsplit('/')[-1], name): ret.append(port) return ret