From 6cc508a9b41056c5553331d1a3380a68811ca155 Mon Sep 17 00:00:00 2001 From: niku64 Date: Mon, 6 Apr 2020 13:47:03 +0300 Subject: [PATCH] Update states/pkg, modules/ebuildpkg (#84) --- sls/_modules/{ebuild.py => ebuildpkg.py} | 455 ++++++++------ sls/_modules/portage_config.py | 735 ----------------------- sls/_states/ini_manage.py | 284 --------- sls/_states/pkg.py | 654 ++++++++++++-------- sls/salt/repos.sls | 23 - sls/salt/roots.sls | 30 +- 6 files changed, 699 insertions(+), 1482 deletions(-) rename sls/_modules/{ebuild.py => ebuildpkg.py} (68%) delete mode 100644 sls/_modules/portage_config.py delete mode 100644 sls/_states/ini_manage.py diff --git a/sls/_modules/ebuild.py b/sls/_modules/ebuildpkg.py similarity index 68% rename from sls/_modules/ebuild.py rename to sls/_modules/ebuildpkg.py index 3947f7d..bbd1e36 100644 --- a/sls/_modules/ebuild.py +++ b/sls/_modules/ebuildpkg.py @@ -13,19 +13,27 @@ Support for Portage For now all package names *MUST* include the package category, i.e. ``'vim'`` will not work, ``'app-editors/vim'`` will. ''' - +from __future__ import absolute_import, print_function, unicode_literals # Import python libs +import os import copy import logging import re -import importlib +import datetime # Import salt libs -import salt.utils +import salt.utils.args +import salt.utils.compat +import salt.utils.data +import salt.utils.functools +import salt.utils.path import salt.utils.pkg +import salt.utils.systemd +import salt.utils.versions from salt.exceptions import CommandExecutionError, MinionError -import salt.ext.six as six +from salt.ext import six + # Import third party libs HAS_PORTAGE = False @@ -49,6 +57,21 @@ log = logging.getLogger(__name__) # Define the module's virtual name __virtualname__ = 'pkg' +r_gt_lt_eq_verstr = re.compile(r'^([<>])?(=)?([^<>=]*)$') +r_split_ver = re.compile(r'^~?([^:\[]+)[\:\[]?.*$') +r_split_repo = re.compile(r'^.+::([^\[]+).*$') +r_strip_ver = re.compile(r'^~?[<>]?=?([^<>=:\[]+).*$') +r_find_upgradeable = re.compile(r'(?m)^\[.+\] ' + r'([^ ]+/[^ ]+)' # Package string + '-' + r'([0-9]+[^ ]+)' # Version + r'.*$') +r_find_changes_in_section = re.compile(r'^[<>=][^ ]+/[^ ]+ [^\n]+', re.M) +r_find_slot_conflicts = re.compile(r'^[^ \n]+/[^ ]+:[^ ]', re.M) +r_find_blocked = re.compile(r'(?m)^\[blocks .+\] ' + r'([^ ]+/[^ ]+-[0-9]+[^ ]+)' + r'.*$') +r_find_unsatisfied = re.compile(r'Error: The above package list contains') def __virtual__(): ''' @@ -61,21 +84,38 @@ def __virtual__(): def _vartree(): import portage # pylint: disable=3rd-party-module-not-gated - portage = importlib.reload(portage) + portage = salt.utils.compat.reload(portage) return portage.db[portage.root]['vartree'] def _porttree(): import portage # pylint: disable=3rd-party-module-not-gated - portage = importlib.reload(portage) + portage = salt.utils.compat.reload(portage) return portage.db[portage.root]['porttree'] def _p_to_cp(p): - ret = _porttree().dbapi.xmatch("match-all", p) - if ret: - return portage.cpv_getkey(ret[0]) - log.error('_p_to_cp: Package {0} not found by xmatch'.format(p)) + try: + ret = portage.dep_getkey(p) + if ret: + return ret + except portage.exception.InvalidAtom: + pass + + try: + ret = _porttree().dbapi.xmatch('bestmatch-visible', p) + if ret: + return portage.dep_getkey(ret) + except portage.exception.InvalidAtom: + pass + + try: + ret = _porttree().dbapi.xmatch("match-all", p) + if ret: + return portage.cpv_getkey(ret[0]) + except portage.exception.InvalidAtom: + pass + return None @@ -89,11 +129,21 @@ def _allnodes(): def _cpv_to_cp(cpv): - ret = portage.cpv_getkey(cpv) - if ret: - return ret - else: - return cpv + try: + ret = portage.dep_getkey(cpv) + if ret: + return ret + except portage.exception.InvalidAtom: + pass + + try: + ret = portage.cpv_getkey(cpv) + if ret: + return ret + except portage.exception.InvalidAtom: + pass + + return cpv def _cpv_to_version(cpv): @@ -105,18 +155,14 @@ def _process_emerge_err(stdout, stderr): Used to parse emerge output to provide meaningful output when emerge fails ''' ret = {} - rexp = re.compile(r'^[<>=][^ ]+/[^ ]+ [^\n]+', re.M) - slot_conflicts = re.compile(r'^[^ \n]+/[^ ]+:[^ ]', re.M).findall(stderr) + slot_conflicts = r_find_slot_conflicts.findall(stderr) if slot_conflicts: ret['slot conflicts'] = slot_conflicts - blocked = re.compile(r'(?m)^\[blocks .+\] ' - r'([^ ]+/[^ ]+-[0-9]+[^ ]+)' - r'.*$').findall(stdout) + blocked = r_find_blocked.findall(stdout) - unsatisfied = re.compile( - r'Error: The above package list contains').findall(stderr) + unsatisfied = r_find_unsatisfied.findall(stderr) # If there were blocks and emerge could not resolve it. if blocked and unsatisfied: @@ -125,13 +171,13 @@ def _process_emerge_err(stdout, stderr): sections = re.split('\n\n', stderr) for section in sections: if 'The following keyword changes' in section: - ret['keywords'] = rexp.findall(section) + ret['keywords'] = r_find_changes_in_section.findall(section) elif 'The following license changes' in section: - ret['license'] = rexp.findall(section) + ret['license'] = r_find_changes_in_section.findall(section) elif 'The following USE changes' in section: - ret['use'] = rexp.findall(section) + ret['use'] = r_find_changes_in_section.findall(section) elif 'The following mask changes' in section: - ret['mask'] = rexp.findall(section) + ret['mask'] = r_find_changes_in_section.findall(section) return ret @@ -220,7 +266,7 @@ def latest_version(*names, **kwargs): salt '*' pkg.latest_version salt '*' pkg.latest_version ... ''' - refresh = salt.utils.is_true(kwargs.pop('refresh', True)) + refresh = salt.utils.data.is_true(kwargs.pop('refresh', True)) if len(names) == 0: return '' @@ -235,7 +281,7 @@ def latest_version(*names, **kwargs): ret[name] = '' installed = _cpv_to_version(_vartree().dep_bestmatch(name)) avail = _cpv_to_version(_porttree().dep_bestmatch(name)) - if avail and (not installed or salt.utils.compare_versions(ver1=installed, oper='<', ver2=avail, cmp_func=version_cmp)): + if avail and (not installed or salt.utils.versions.compare(ver1=installed, oper='<', ver2=avail, cmp_func=version_cmp)): ret[name] = avail # Return a string if only one package name passed @@ -244,6 +290,10 @@ def latest_version(*names, **kwargs): return ret +# available_version is being deprecated +available_version = salt.utils.functools.alias_function(latest_version, 'available_version') + + def _get_upgradable(backtrack=3): ''' Utility function to get upgradable packages @@ -275,15 +325,10 @@ def _get_upgradable(backtrack=3): else: out = call['stdout'] - rexp = re.compile(r'(?m)^\[.+\] ' - r'([^ ]+/[^ ]+)' # Package string - '-' - r'([0-9]+[^ ]+)' # Version - r'.*$') keys = ['name', 'version'] _get = lambda l, k: l[keys.index(k)] - upgrades = rexp.findall(out) + upgrades = r_find_upgradeable.findall(out) ret = {} for line in upgrades: @@ -314,7 +359,7 @@ def list_upgrades(refresh=True, backtrack=3, **kwargs): # pylint: disable=W0613 salt '*' pkg.list_upgrades ''' - if salt.utils.is_true(refresh): + if salt.utils.data.is_true(refresh): refresh_db() return _get_upgradable(backtrack) @@ -345,21 +390,8 @@ def version(*names, **kwargs): salt '*' pkg.version salt '*' pkg.version ... ''' - if len(names) == 0: - return '' + return __salt__['pkg_resource.version'](*names, **kwargs) - ret = {} - # Initialize the dict with empty strings - for name in names: - ret[name] = '' - installed = _cpv_to_version(_vartree().dep_bestmatch(name)) - if installed: - ret[name] = installed - - # Return a string if only one package name passed - if len(names) == 1: - return ret[names[0]] - return ret def porttree_matches(name): ''' @@ -387,9 +419,9 @@ def list_pkgs(versions_as_list=False, **kwargs): salt '*' pkg.list_pkgs ''' - versions_as_list = salt.utils.is_true(versions_as_list) + versions_as_list = salt.utils.data.is_true(versions_as_list) # not yet implemented or not applicable - if any([salt.utils.is_true(kwargs.get(x)) + if any([salt.utils.data.is_true(kwargs.get(x)) for x in ('removed', 'purge_desired')]): return {} @@ -416,7 +448,21 @@ def list_pkgs(versions_as_list=False, **kwargs): def refresh_db(): ''' - Updates the portage tree (emerge --sync). Uses eix-sync if available. + Update the portage tree using the first available method from the following + list: + + - emaint sync + - eix-sync + - emerge-webrsync + - emerge --sync + + To prevent the portage tree from being synced within one day of the + previous sync, add the following pillar data for this minion: + + .. code-block:: yaml + + portage: + sync_wait_one_day: True CLI Example: @@ -424,28 +470,37 @@ def refresh_db(): salt '*' pkg.refresh_db ''' + has_emaint = os.path.isdir('/etc/portage/repos.conf') + has_eix = True if 'eix.sync' in __salt__ else False + has_webrsync = True if __salt__['makeconf.features_contains']('webrsync-gpg') else False + # Remove rtag file to keep multiple refreshes from happening in pkg states salt.utils.pkg.clear_rtag(__opts__) - if 'eix.sync' in __salt__: - return __salt__['eix.sync']() + # Option to prevent syncing package tree if done in the last 24 hours + if __salt__['pillar.get']('portage:sync_wait_one_day', False): + main_repo_root = __salt__['cmd.run']('portageq get_repo_path / gentoo') + day = datetime.timedelta(days=1) + now = datetime.datetime.now() + timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(main_repo_root)) + if now - timestamp < day: + log.info('Did not sync package tree since last sync was done at' + ' {0}, less than 1 day ago'.format(timestamp)) + return False - if 'makeconf.features_contains'in __salt__ and __salt__['makeconf.features_contains']('webrsync-gpg'): - # GPG sign verify is supported only for "webrsync" + if has_emaint: + return __salt__['cmd.retcode']('emaint sync -a') == 0 + elif has_eix: + return __salt__['eix.sync']() + elif has_webrsync: + # GPG sign verify is supported only for 'webrsync' cmd = 'emerge-webrsync -q' - # We prefer 'delta-webrsync' to 'webrsync' - if salt.utils.which('emerge-delta-webrsync'): + # Prefer 'delta-webrsync' to 'webrsync' + if salt.utils.path.which('emerge-delta-webrsync'): cmd = 'emerge-delta-webrsync -q' - return __salt__['cmd.retcode'](cmd, python_shell=False) == 0 + return __salt__['cmd.retcode'](cmd) == 0 else: - if __salt__['cmd.retcode']('emerge --ask n --quiet --sync', - python_shell=False) == 0: - return True - # We fall back to "webrsync" if "rsync" fails for some reason - cmd = 'emerge-webrsync -q' - # We prefer 'delta-webrsync' to 'webrsync' - if salt.utils.which('emerge-delta-webrsync'): - cmd = 'emerge-delta-webrsync -q' - return __salt__['cmd.retcode'](cmd, python_shell=False) == 0 + # Default to deprecated `emerge --sync` form + return __salt__['cmd.retcode']('emerge --ask n --quiet --sync') == 0 def _flags_changed(inst_flags, conf_flags): @@ -466,72 +521,6 @@ def _flags_changed(inst_flags, conf_flags): return True return True if conf_flags else False -def process_target(param, version_num): - installed = _cpv_to_version(_vartree().dep_bestmatch(param)) - if version_num is None: - keyword, prefix = None, None - target = param - else: - match = re.match('^(~|-|\*)?([<>]?=?)?([^<>=]*)$', version_num) - if match: - keyword, prefix, verstr = match.groups() - # If no prefix characters were supplied and verstr contains a version, use '=' - if len(verstr) > 0 and verstr[0] != ':' and verstr[0] != '[': - prefix = prefix or '=' - target = '{0}{1}-{2}'.format(prefix, param, verstr) - else: - target = '{0}{1}'.format(param, verstr) - else: - raise AttributeError( - 'Unable to parse version {0} of {1}'.\ - format(repr(version_num), param)) - - changes = {} - - if '[' in target: - # Clean target from use flags, since some of them may not exist. - # This will raise an AttributeError, or some weird stack trace if portage_config is not patched. - uses = portage.dep.dep_getusedeps(target) - pv_target = target[:target.rfind('[')] - if installed: - old = __salt__['portage_config.get_flags_from_package_conf']('use', pv_target) - else: - old = [] - __salt__['portage_config.append_use_flags'](pv_target, uses) - new = __salt__['portage_config.get_flags_from_package_conf']('use', pv_target) - if old != new: - changes[pv_target] = {'old': {'use': old}, - 'new': {'use': new}} - else: - pv_target = target - - if keyword is not None: - if keyword == '~': - kws = ['~*'] - elif keyword == '*': - kws = ['**'] - elif keyword == '-': - # Or [''] ? - kws = ['~ARCH'] - else: - kws = [keyword] - if installed: - old = __salt__['portage_config.get_flags_from_package_conf']('accept_keywords', pv_target) - else: - old = [] - __salt__['portage_config.append_to_package_conf']('accept_keywords', pv_target, kws) - new = __salt__['portage_config.get_flags_from_package_conf']('accept_keywords', pv_target) - if old != new: - changes[pv_target] = {'old': {'accept_keywords': old}, - 'new': {'accept_keywords': new}} - - if installed and not changes: - # Check if package has some changed use. - all_uses = __salt__['portage_config.get_cleared_flags'](param) - if _flags_changed(*all_uses): - changes[pv_target] = {'old': {'use': all_uses[0]}, - 'new': {'use': all_uses[1]}} - return target, changes def install(name=None, refresh=False, @@ -543,6 +532,20 @@ def install(name=None, binhost=None, **kwargs): ''' + .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 + On minions running systemd>=205, `systemd-run(1)`_ is now used to + isolate commands which modify installed packages from the + ``salt-minion`` daemon's control group. This is done to keep systemd + from killing any emerge commands spawned by Salt when the + ``salt-minion`` service is restarted. (see ``KillMode`` in the + `systemd.kill(5)`_ manpage for more information). If desired, usage of + `systemd-run(1)`_ can be suppressed by setting a :mod:`config option + ` called ``systemd.scope``, with a value of + ``False`` (no quotes). + + .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html + .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html + Install the passed package(s), add refresh=True to sync the portage tree before package is installed. @@ -642,7 +645,7 @@ def install(name=None, 'binhost': binhost, } )) - if salt.utils.is_true(refresh): + if salt.utils.data.is_true(refresh): refresh_db() try: @@ -655,9 +658,7 @@ def install(name=None, # Handle version kwarg for a single package target if pkgs is None and sources is None: version_num = kwargs.get('version') - if version_num: - pkg_params = {name: version_num} - else: + if not version_num: version_num = '' if slot is not None: version_num += ':{0}'.format(slot) @@ -681,27 +682,60 @@ def install(name=None, else: bin_opts = [] - if 'oneshot' in kwargs and kwargs.get('oneshot') == True: - oneshot = ['--oneshot'] - else: - oneshot = [] - changes = {} - if not pkg_type == 'repository': - targets = pkg_params - else: + if pkg_type == 'repository': targets = list() for param, version_num in six.iteritems(pkg_params): - target, flag_changes = process_target(param, version_num) - changes.update(flag_changes) - targets.append(target) + original_param = param + param = _p_to_cp(param) + if param is None: + raise portage.dep.InvalidAtom(original_param) + + if version_num is None: + targets.append(param) + else: + keyword = None + + match = re.match(r_gt_lt_eq_verstr, version_num) + if match: + gt_lt, eq, verstr = match.groups() + prefix = gt_lt or '' + prefix += eq or '' + # If no prefix characters were supplied and verstr contains a version, use '=' + if len(verstr) > 0 and verstr[0] != ':' and verstr[0] != '[': + prefix = prefix or '=' + target = '{0}{1}-{2}'.format(prefix, param, verstr) + else: + target = '{0}{1}'.format(param, verstr) + else: + target = '{0}'.format(param) + + if '[' in target: + target = target[:target.rfind('[')] + + if not changes: + inst_v = version(param) + + # Prevent latest_version from calling refresh_db. Either we + # just called it or we were asked not to. + if latest_version(param, refresh=False) == inst_v: + all_uses = __salt__['portage_config.get_cleared_flags'](param) + if _flags_changed(*all_uses): + changes[param] = {'version': inst_v, + 'old': {'use': all_uses[0]}, + 'new': {'use': all_uses[1]}} + targets.append(target) + else: + targets = pkg_params cmd = [] + if salt.utils.systemd.has_scope(__context__) \ + and __salt__['config.get']('systemd.scope', True): + cmd.extend(['systemd-run', '--scope']) cmd.extend(['emerge', '--ask', 'n', '--quiet']) cmd.extend(bin_opts) cmd.extend(emerge_opts) - cmd.extend(oneshot) cmd.extend(targets) old = list_pkgs() @@ -715,22 +749,33 @@ def install(name=None, __context__.pop('pkg.list_pkgs', None) new = list_pkgs() - changes.update(salt.utils.compare_dicts(old, new)) + changes.update(salt.utils.data.compare_dicts(old, new)) if needed_changes: raise CommandExecutionError( 'Error occurred installing package(s)', info={'needed changes': needed_changes, 'changes': changes} ) - if call['retcode'] != 0: - raise CommandExecutionError( - 'unknown error stdout: {}, stderr: {}'.format(call['stdout'], call['stderr']) - ) + return changes def update(pkg, slot=None, fromrepo=None, refresh=False, binhost=None): ''' + .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 + On minions running systemd>=205, `systemd-run(1)`_ is now used to + isolate commands which modify installed packages from the + ``salt-minion`` daemon's control group. This is done to keep systemd + from killing any emerge commands spawned by Salt when the + ``salt-minion`` service is restarted. (see ``KillMode`` in the + `systemd.kill(5)`_ manpage for more information). If desired, usage of + `systemd-run(1)`_ can be suppressed by setting a :mod:`config option + ` called ``systemd.scope``, with a value of + ``False`` (no quotes). + + .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html + .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html + Updates the passed package (emerge --update package) slot @@ -756,7 +801,7 @@ def update(pkg, slot=None, fromrepo=None, refresh=False, binhost=None): salt '*' pkg.update ''' - if salt.utils.is_true(refresh): + if salt.utils.data.is_true(refresh): refresh_db() full_atom = pkg @@ -768,14 +813,17 @@ def update(pkg, slot=None, fromrepo=None, refresh=False, binhost=None): full_atom = '{0}::{1}'.format(full_atom, fromrepo) if binhost == 'try': - bin_opts = ['--getbinpkg'] + bin_opts = ['-g'] elif binhost == 'force': - bin_opts = ['--getbinpkgonly'] + bin_opts = ['-G'] else: bin_opts = [] old = list_pkgs() cmd = [] + if salt.utils.systemd.has_scope(__context__) \ + and __salt__['config.get']('systemd.scope', True): + cmd.extend(['systemd-run', '--scope']) cmd.extend(['emerge', '--ask', 'n', '--quiet', @@ -794,7 +842,7 @@ def update(pkg, slot=None, fromrepo=None, refresh=False, binhost=None): __context__.pop('pkg.list_pkgs', None) new = list_pkgs() - ret = salt.utils.compare_dicts(old, new) + ret = salt.utils.data.compare_dicts(old, new) if needed_changes: raise CommandExecutionError( @@ -807,6 +855,20 @@ def update(pkg, slot=None, fromrepo=None, refresh=False, binhost=None): def upgrade(refresh=True, binhost=None, backtrack=3): ''' + .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 + On minions running systemd>=205, `systemd-run(1)`_ is now used to + isolate commands which modify installed packages from the + ``salt-minion`` daemon's control group. This is done to keep systemd + from killing any emerge commands spawned by Salt when the + ``salt-minion`` service is restarted. (see ``KillMode`` in the + `systemd.kill(5)`_ manpage for more information). If desired, usage of + `systemd-run(1)`_ can be suppressed by setting a :mod:`config option + ` called ``systemd.scope``, with a value of + ``False`` (no quotes). + + .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html + .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html + Run a full system upgrade (emerge -uDN @world) binhost @@ -839,7 +901,7 @@ def upgrade(refresh=True, binhost=None, backtrack=3): 'result': True, 'comment': ''} - if salt.utils.is_true(refresh): + if salt.utils.data.is_true(refresh): refresh_db() if binhost == 'try': @@ -851,6 +913,9 @@ def upgrade(refresh=True, binhost=None, backtrack=3): old = list_pkgs() cmd = [] + if salt.utils.systemd.has_scope(__context__) \ + and __salt__['config.get']('systemd.scope', True): + cmd.extend(['systemd-run', '--scope']) cmd.extend(['emerge', '--ask', 'n', '--quiet', @@ -867,7 +932,7 @@ def upgrade(refresh=True, binhost=None, backtrack=3): python_shell=False) __context__.pop('pkg.list_pkgs', None) new = list_pkgs() - ret = salt.utils.compare_dicts(old, new) + ret = salt.utils.data.compare_dicts(old, new) if result['retcode'] != 0: raise CommandExecutionError( @@ -880,6 +945,20 @@ def upgrade(refresh=True, binhost=None, backtrack=3): def remove(name=None, slot=None, fromrepo=None, pkgs=None, **kwargs): ''' + .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 + On minions running systemd>=205, `systemd-run(1)`_ is now used to + isolate commands which modify installed packages from the + ``salt-minion`` daemon's control group. This is done to keep systemd + from killing any emerge commands spawned by Salt when the + ``salt-minion`` service is restarted. (see ``KillMode`` in the + `systemd.kill(5)`_ manpage for more information). If desired, usage of + `systemd-run(1)`_ can be suppressed by setting a :mod:`config option + ` called ``systemd.scope``, with a value of + ``False`` (no quotes). + + .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html + .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html + Remove packages via emerge --unmerge. name @@ -930,6 +1009,9 @@ def remove(name=None, slot=None, fromrepo=None, pkgs=None, **kwargs): return {} cmd = [] + if salt.utils.systemd.has_scope(__context__) \ + and __salt__['config.get']('systemd.scope', True): + cmd.extend(['systemd-run', '--scope']) cmd.extend(['emerge', '--ask', 'n', '--quiet', @@ -949,7 +1031,7 @@ def remove(name=None, slot=None, fromrepo=None, pkgs=None, **kwargs): __context__.pop('pkg.list_pkgs', None) new = list_pkgs() - ret = salt.utils.compare_dicts(old, new) + ret = salt.utils.data.compare_dicts(old, new) if errors: raise CommandExecutionError( @@ -962,6 +1044,20 @@ def remove(name=None, slot=None, fromrepo=None, pkgs=None, **kwargs): def purge(name=None, slot=None, fromrepo=None, pkgs=None, **kwargs): ''' + .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 + On minions running systemd>=205, `systemd-run(1)`_ is now used to + isolate commands which modify installed packages from the + ``salt-minion`` daemon's control group. This is done to keep systemd + from killing any emerge commands spawned by Salt when the + ``salt-minion`` service is restarted. (see ``KillMode`` in the + `systemd.kill(5)`_ manpage for more information). If desired, usage of + `systemd-run(1)`_ can be suppressed by setting a :mod:`config option + ` called ``systemd.scope``, with a value of + ``False`` (no quotes). + + .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html + .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html + Portage does not have a purge, this function calls remove followed by depclean to emulate a purge process @@ -1048,7 +1144,7 @@ def depclean(name=None, slot=None, fromrepo=None, pkgs=None): python_shell=False) __context__.pop('pkg.list_pkgs', None) new = list_pkgs() - return salt.utils.compare_dicts(old, new) + return salt.utils.data.compare_dicts(old, new) def version_cmp(pkg1, pkg2, **kwargs): @@ -1068,14 +1164,13 @@ def version_cmp(pkg1, pkg2, **kwargs): # definition (and thus have it show up in the docs), we just pop it out of # the kwargs dict and then raise an exception if any kwargs other than # ignore_epoch were passed. - kwargs = salt.utils.clean_kwargs(**kwargs) + kwargs = salt.utils.args.clean_kwargs(**kwargs) kwargs.pop('ignore_epoch', None) if kwargs: - salt.utils.invalid_kwargs(kwargs) + salt.utils.args.invalid_kwargs(kwargs) - regex = r'^(?:~|-|\*)?([^:\[\~\*]+):?[^\[]*\[?.*$' - ver1 = re.match(regex, pkg1) - ver2 = re.match(regex, pkg2) + ver1 = r_split_ver.match(pkg1) + ver2 = r_split_ver.match(pkg2) if ver1 and ver2: return portage.versions.vercmp(ver1.group(1), ver2.group(1)) @@ -1092,7 +1187,7 @@ def version_clean(version): salt '*' pkg.version_clean ''' - return re.match(r'^(?:~|-|\*)?[<>]?=?([^<>=:\[\~\-\*]+).*$', version) + return re.match(r_strip_ver, version) def check_extra_requirements(pkgname, pkgver): @@ -1107,13 +1202,15 @@ def check_extra_requirements(pkgname, pkgver): ''' keyword = None - match = re.match('^(~|-|\*)?([<>])?(=)?([^<>=]*)$', pkgver) + match = re.match('^(~)?([<>])?(=)?([^<>=]*)$', pkgver) if match: keyword, gt_lt, eq, verstr = match.groups() prefix = gt_lt or '' prefix += eq or '' + # We need to delete quotes around use flag list elements + verstr = verstr.replace("'", "") # If no prefix characters were supplied and verstr contains a version, use '=' - if len(verstr) > 0 and verstr[0] != ':' and verstr[0] != '[': + if verstr[0] != ':' and verstr[0] != '[': prefix = prefix or '=' atom = '{0}{1}-{2}'.format(prefix, pkgname, verstr) else: @@ -1135,26 +1232,16 @@ def check_extra_requirements(pkgname, pkgver): except KeyError: return False - des_repo = re.match(r'^.+::([^\[\~\-\*]+).*$', atom) + des_repo = re.match(r_split_repo, atom) if des_repo and des_repo.group(1) != cur_repo: return False des_uses = set(portage.dep.dep_getusedeps(atom)) cur_use = cur_use.split() + # TODO: clarify this filter + # Filters out -flag that is not currently enabled thus not in cur_use if len([x for x in des_uses.difference(cur_use) if x[0] != '-' or x[1:] in cur_use]) > 0: return False - if keyword is not None: - if keyword == '~': - v = '~*' - elif keyword == '*': - v = '**' - elif keyword == '-': - v = '~ARCH' - else: - v = keyword - if not __salt__['portage_config.has_flag']('accept_keywords', atom, v): - return False - return True diff --git a/sls/_modules/portage_config.py b/sls/_modules/portage_config.py deleted file mode 100644 index fc00ecb..0000000 --- a/sls/_modules/portage_config.py +++ /dev/null @@ -1,735 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Configure ``portage(5)`` -''' - -# Import python libs - -import logging -import os -import shutil -import importlib - -# Import salt libs -import salt.utils -from salt.utils.files import fopen - -# Import third party libs -import salt.ext.six as six -# pylint: disable=import-error -try: - import portage - HAS_PORTAGE = True -except ImportError: - HAS_PORTAGE = False - import sys - if os.path.isdir('/usr/lib/portage/pym'): - try: - # In a virtualenv, the portage python path needs to be manually - # added - sys.path.insert(0, '/usr/lib/portage/pym') - import portage - HAS_PORTAGE = True - except ImportError: - pass -# pylint: enable=import-error - - -BASE_PATH = '/etc/portage/package.{0}' -SUPPORTED_CONFS = ('accept_keywords', 'env', 'license', 'mask', 'properties', - 'unmask', 'use') - -log = logging.getLogger(__name__) - - -def __virtual__(): - ''' - Confirm this module is on a Gentoo based system. - ''' - if HAS_PORTAGE and __grains__['os'] == 'Gentoo': - return 'portage_config' - return (False, 'portage_config execution module cannot be loaded: only available on Gentoo with portage installed.') - - -def _get_portage(): - ''' - portage module must be reloaded or it can't catch the changes - in portage.* which had been added after when the module was loaded - ''' - return importlib.reload(portage) - - -def _porttree(): - return portage.db[portage.root]['porttree'] - - -def _get_config_file(conf, atom): - ''' - Parse the given atom, allowing access to its parts - Success does not mean that the atom exists, just that it - is in the correct format. - Returns none if the atom is invalid. - ''' - if '*' in atom: - parts = portage.dep.Atom(atom, allow_wildcard=True) - if not parts: - raise AttributeError('Matching c/p form not found for atom {0}'.format(atom)) - if parts.cp == '*/*': - # parts.repo will be empty if there is no repo part - relative_path = parts.repo or "gentoo" - else: - relative_path = os.path.join(*[x for x in os.path.split(parts.cp) if x != '*']) - else: - relative_path = _p_to_cp(atom) - if not relative_path: - raise AttributeError('Matching c/p form not found for atom {0}'.format(atom)) - - complete_file_path = BASE_PATH.format(conf) + '/' + relative_path - - return complete_file_path - - -def _p_to_cp(p): - ''' - Convert a package name or a DEPEND atom to category/package format. - Raises an exception if program name is ambiguous. - ''' - ret = _porttree().dbapi.xmatch("match-all", p) - if ret: - return portage.cpv_getkey(ret[0]) - log.error('_p_to_cp: Package {0} not found by xmatch'.format(p)) - return None - - -def _get_cpv(cp, installed=True): - ''' - add version to category/package - @cp - name of package in format category/name - @installed - boolean value, if False, function returns cpv - for latest available package - ''' - if installed: - return _get_portage().db[portage.root]['vartree'].dep_bestmatch(cp) - else: - return _porttree().dep_bestmatch(cp) - - -def enforce_nice_config(): - ''' - Enforce a nice tree structure for /etc/portage/package.* configuration - files. - - .. seealso:: - :py:func:`salt.modules.ebuild.ex_mod_init` - for information on automatically running this when pkg is used. - - - CLI Example: - - .. code-block:: bash - - salt '*' portage_config.enforce_nice_config - ''' - _convert_all_package_confs_to_dir() - _order_all_package_confs() - - -def _convert_all_package_confs_to_dir(): - ''' - Convert all /etc/portage/package.* configuration files to directories. - ''' - for conf_file in SUPPORTED_CONFS: - _package_conf_file_to_dir(conf_file) - - -def _order_all_package_confs(): - ''' - Place all entries in /etc/portage/package.* config dirs in the correct - file. - ''' - for conf_file in SUPPORTED_CONFS: - _package_conf_ordering(conf_file) - _unify_keywords() - - -def _unify_keywords(): - ''' - Merge /etc/portage/package.keywords and - /etc/portage/package.accept_keywords. - ''' - old_path = BASE_PATH.format('keywords') - if os.path.exists(old_path): - if os.path.isdir(old_path): - for triplet in os.walk(old_path): - for file_name in triplet[2]: - file_path = '{0}/{1}'.format(triplet[0], file_name) - with fopen(file_path) as fh_: - for line in fh_: - line = line.strip() - if line and not line.startswith('#'): - append_to_package_conf( - 'accept_keywords', string=line) - shutil.rmtree(old_path) - else: - with fopen(old_path) as fh_: - for line in fh_: - line = line.strip() - if line and not line.startswith('#'): - append_to_package_conf('accept_keywords', string=line) - os.remove(old_path) - - -def _package_conf_file_to_dir(file_name): - ''' - Convert a config file to a config directory. - ''' - if file_name in SUPPORTED_CONFS: - path = BASE_PATH.format(file_name) - if os.path.exists(path): - if os.path.isdir(path): - return False - else: - os.rename(path, path + '.tmpbak') - os.mkdir(path, 0o755) - with fopen(path + '.tmpbak') as fh_: - for line in fh_: - line = line.strip() - if line and not line.startswith('#'): - append_to_package_conf(file_name, string=line) - os.remove(path + '.tmpbak') - return True - else: - os.mkdir(path, 0o755) - return True - - -def _package_conf_ordering(conf, clean=True, keep_backup=False): - ''' - Move entries in the correct file. - ''' - if conf in SUPPORTED_CONFS: - rearrange = [] - path = BASE_PATH.format(conf) - - backup_files = [] - - for triplet in os.walk(path): - for file_name in triplet[2]: - file_path = '{0}/{1}'.format(triplet[0], file_name) - cp = triplet[0][len(path) + 1:] + '/' + file_name - - shutil.copy(file_path, file_path + '.bak') - backup_files.append(file_path + '.bak') - - if cp[0] == '/' or cp.split('/') > 2: - with fopen(file_path) as fp_: - rearrange.extend(fp_.readlines()) - os.remove(file_path) - else: - new_contents = '' - with fopen(file_path, 'r+') as file_handler: - for line in file_handler: - try: - atom = line.strip().split()[0] - except IndexError: - new_contents += line - else: - if atom[0] == '#' or \ - portage.dep_getkey(atom) == cp: - new_contents += line - else: - rearrange.append(line.strip()) - if len(new_contents) != 0: - file_handler.seek(0) - file_handler.truncate(len(new_contents)) - file_handler.write(new_contents) - - if len(new_contents) == 0: - os.remove(file_path) - - for line in rearrange: - append_to_package_conf(conf, string=line) - - if not keep_backup: - for bfile in backup_files: - try: - os.remove(bfile) - except OSError: - pass - - if clean: - for triplet in os.walk(path): - if len(triplet[1]) == 0 and len(triplet[2]) == 0 and \ - triplet[0] != path: - shutil.rmtree(triplet[0]) - - -def _check_accept_keywords(approved, flag): - '''check compatibility of accept_keywords''' - if flag in approved: - return False - elif (flag.startswith('~') and flag[1:] in approved) \ - or ('~'+flag in approved): - return False - else: - return True - - -def _merge_flags(new_flags, old_flags=None, conf='any'): - ''' - Merges multiple lists of flags removing duplicates and resolving conflicts - giving priority to lasts lists. - ''' - if not old_flags: - old_flags = [] - args = [old_flags, new_flags] - if conf == 'accept_keywords': - tmp = new_flags + \ - [i for i in old_flags if _check_accept_keywords(new_flags, i)] - else: - tmp = portage.flatten(args) - flags = {} - for flag in tmp: - if flag[0] == '-': - flags[flag[1:]] = False - else: - flags[flag] = True - tmp = [] - for key, val in six.iteritems(flags): - if val: - tmp.append(key) - else: - tmp.append('-' + key) - - # Next sort is just aesthetic, can be commented for a small performance - # boost - tmp.sort(key=lambda x: x.lstrip('-')) - return tmp - - -def append_to_package_conf(conf, atom='', flags=None, string='', overwrite=False): - ''' - Append a string or a list of flags for a given package or DEPEND atom to a - given configuration file. - - CLI Example: - - .. code-block:: bash - - salt '*' portage_config.append_to_package_conf use string="app-admin/salt ldap -libvirt" - salt '*' portage_config.append_to_package_conf use atom="> = app-admin/salt-0.14.1" flags="['ldap', '-libvirt']" - ''' - if flags is None: - flags = [] - if conf in SUPPORTED_CONFS: - if not string: - if '/' not in atom: - atom = _p_to_cp(atom) - if not atom: - return - string = '{0} {1}'.format(atom, ' '.join(flags)) - new_flags = list(flags) - else: - atom = string.strip().split()[0] - new_flags = [flag for flag in string.strip().split(' ') if flag][1:] - if '/' not in atom: - atom = _p_to_cp(atom) - string = '{0} {1}'.format(atom, ' '.join(new_flags)) - if not atom: - return - - to_delete_if_empty = [] - if conf == 'accept_keywords': - if '-~ARCH' in new_flags: - new_flags.remove('-~ARCH') - to_delete_if_empty.append(atom) - - if '~ARCH' in new_flags: - new_flags.remove('~ARCH') - append_to_package_conf(conf, string=atom, overwrite=overwrite) - if not new_flags: - return - - # Next sort is just aesthetic, can be commented for a small performance - # boost - new_flags.sort(key=lambda x: x.lstrip('-')) - - complete_file_path = _get_config_file(conf, atom) - pdir = os.path.dirname(complete_file_path) - if not os.path.exists(pdir): - os.makedirs(pdir, 0o755) - - try: - shutil.copy(complete_file_path, complete_file_path + '.bak') - except IOError: - pass - - try: - file_handler = fopen(complete_file_path, 'r+') # pylint: disable=resource-leakage - except IOError: - file_handler = fopen(complete_file_path, 'w+') # pylint: disable=resource-leakage - - new_contents = '' - added = False - - try: - for l in file_handler: - l_strip = l.strip() - if l_strip == '': - new_contents += '\n' - elif l_strip[0] == '#': - new_contents += l - elif l_strip.split()[0] == atom: - if l_strip in to_delete_if_empty: - continue - if overwrite: - new_contents += string.strip() + '\n' - added = True - else: - old_flags = [flag for flag in l_strip.split(' ') if flag][1:] - if conf == 'accept_keywords': - if not old_flags: - new_contents += l - if not new_flags: - added = True - continue - elif not new_flags: - continue - merged_flags = _merge_flags(new_flags, old_flags, conf) - if merged_flags: - new_contents += '{0} {1}\n'.format( - atom, ' '.join(merged_flags)) - else: - new_contents += '{0}\n'.format(atom) - added = True - else: - new_contents += l - if not added: - new_contents += string.strip() + '\n' - except Exception as exc: - log.error('Failed to write to %s: %s', complete_file_path, exc) - else: - file_handler.seek(0) - file_handler.truncate(len(new_contents)) - file_handler.write(new_contents) - finally: - file_handler.close() - - try: - os.remove(complete_file_path + '.bak') - except OSError: - pass - - -def append_use_flags(atom, uses=None, overwrite=False): - ''' - Append a list of use flags for a given package or DEPEND atom - - CLI Example: - - .. code-block:: bash - - salt '*' portage_config.append_use_flags "app-admin/salt[ldap, -libvirt]" - salt '*' portage_config.append_use_flags ">=app-admin/salt-0.14.1" "['ldap', '-libvirt']" - ''' - if not uses: - uses = portage.dep.dep_getusedeps(atom) - atom = atom[:atom.rfind('[')] - if len(uses) == 0: - return - append_to_package_conf('use', atom=atom, flags=uses, overwrite=overwrite) - - -def get_flags_from_package_conf(conf, atom): - ''' - Get flags for a given package or DEPEND atom. - Warning: This only works if the configuration files tree is in the correct - format (the one enforced by enforce_nice_config) - - CLI Example: - - .. code-block:: bash - - salt '*' portage_config.get_flags_from_package_conf license salt - ''' - if conf in SUPPORTED_CONFS: - if '/' not in atom: - atom = _p_to_cp(atom) - if not atom: - raise AttributeError('Matching c/p form not found for atom {0}'.format(atom)) - package_file = _get_config_file(conf, atom) - - has_wildcard = '*' in atom - if has_wildcard: - match_list = set(atom) - else: - try: - match_list = set(_porttree().dbapi.xmatch("match-all", atom)) - except AttributeError: - return [] - - flags = [] - try: - with fopen(package_file) as fp_: - for line in fp_: - line = line.strip() - line_package = line.split()[0] - - if has_wildcard: - found_match = line_package == atom - else: - line_list = _porttree().dbapi.xmatch("match-all", line_package) - found_match = match_list.issubset(line_list) - - if found_match: - f_tmp = [flag for flag in line.strip().split(' ') if flag][1:] - if f_tmp: - flags.extend(f_tmp) - else: - flags.append('~ARCH') - - return _merge_flags(flags) - except IOError: - return [] - - -def has_flag(conf, atom, flag): - ''' - Verify if the given package or DEPEND atom has the given flag. - Warning: This only works if the configuration files tree is in the correct - format (the one enforced by enforce_nice_config) - - CLI Example: - - .. code-block:: bash - - salt '*' portage_config.has_flag license salt Apache-2.0 - ''' - if flag in get_flags_from_package_conf(conf, atom): - return True - return False - - -def get_missing_flags(conf, atom, flags): - ''' - Find out which of the given flags are currently not set. - CLI Example: - - .. code-block:: bash - - salt '*' portage_config.get_missing_flags use salt "['ldap', '-libvirt', 'openssl']" - ''' - new_flags = [] - for flag in flags: - if not has_flag(conf, atom, flag): - new_flags.append(flag) - return new_flags - - -def has_use(atom, use): - ''' - Verify if the given package or DEPEND atom has the given use flag. - Warning: This only works if the configuration files tree is in the correct - format (the one enforced by enforce_nice_config) - - CLI Example: - - .. code-block:: bash - - salt '*' portage_config.has_use salt libvirt - ''' - return has_flag('use', atom, use) - - -def is_present(conf, atom): - ''' - Tell if a given package or DEPEND atom is present in the configuration - files tree. - Warning: This only works if the configuration files tree is in the correct - format (the one enforced by enforce_nice_config) - - CLI Example: - - .. code-block:: bash - - salt '*' portage_config.is_present unmask salt - ''' - if conf in SUPPORTED_CONFS: - if not isinstance(atom, portage.dep.Atom): - atom = portage.dep.Atom(atom, allow_wildcard=True) - has_wildcard = '*' in atom - - package_file = _get_config_file(conf, str(atom)) - - # wildcards are valid in confs - if has_wildcard: - match_list = set(atom) - else: - match_list = set(_porttree().dbapi.xmatch("match-all", atom)) - - try: - with fopen(package_file) as fp_: - for line in fp_: - line = line.strip() - line_package = line.split()[0] - - if has_wildcard: - if line_package == str(atom): - return True - else: - line_list = _porttree().dbapi.xmatch("match-all", line_package) - if match_list.issubset(line_list): - return True - except IOError: - pass - return False - - -def get_iuse(cp): - ''' - .. versionadded:: 2015.8.0 - - Gets the current IUSE flags from the tree. - - @type: cpv: string - @param cpv: cat/pkg - @rtype list - @returns [] or the list of IUSE flags - ''' - cpv = _get_cpv(cp) - try: - # aux_get might return dupes, so run them through set() to remove them - dirty_flags = _porttree().dbapi.aux_get(cpv, ["IUSE"])[0].split() - return list(set(dirty_flags)) - except Exception as e: - return [] - - -def get_installed_use(cp, use="USE"): - ''' - .. versionadded:: 2015.8.0 - - Gets the installed USE flags from the VARDB. - - @type: cp: string - @param cp: cat/pkg - @type use: string - @param use: 1 of ["USE", "PKGUSE"] - @rtype list - @returns [] or the list of IUSE flags - ''' - portage = _get_portage() - cpv = _get_cpv(cp) - return portage.db[portage.root]["vartree"].dbapi.aux_get(cpv, [use])[0].split() - - -def filter_flags(use, use_expand_hidden, usemasked, useforced): - ''' - .. versionadded:: 2015.8.0 - - Filter function to remove hidden or otherwise not normally - visible USE flags from a list. - - @type use: list - @param use: the USE flag list to be filtered. - @type use_expand_hidden: list - @param use_expand_hidden: list of flags hidden. - @type usemasked: list - @param usemasked: list of masked USE flags. - @type useforced: list - @param useforced: the forced USE flags. - @rtype: list - @return the filtered USE flags. - ''' - portage = _get_portage() - # clean out some environment flags, since they will most probably - # be confusing for the user - for f in use_expand_hidden: - f = f.lower()+ "_" - for x in use: - if f in x: - use.remove(x) - # clean out any arch's - archlist = portage.settings["PORTAGE_ARCHLIST"].split() - for a in use[:]: - if a in archlist: - use.remove(a) - # dbl check if any from usemasked or useforced are still there - masked = usemasked + useforced - for a in use[:]: - if a in masked: - use.remove(a) - return use - - -def get_all_cpv_use(cp): - ''' - .. versionadded:: 2015.8.0 - - Uses portage to determine final USE flags and settings for an emerge. - - @type cp: string - @param cp: eg cat/pkg - @rtype: lists - @return use, use_expand_hidden, usemask, useforce - ''' - cpv = _get_cpv(cp) - portage = _get_portage() - use = None - _porttree().dbapi.settings.unlock() - try: - _porttree().dbapi.settings.setcpv(cpv, mydb=portage.portdb) - use = portage.settings['PORTAGE_USE'].split() - use_expand_hidden = portage.settings["USE_EXPAND_HIDDEN"].split() - usemask = list(_porttree().dbapi.settings.usemask) - useforce = list(_porttree().dbapi.settings.useforce) - except KeyError: - _porttree().dbapi.settings.reset() - _porttree().dbapi.settings.lock() - return [], [], [], [] - # reset cpv filter - _porttree().dbapi.settings.reset() - _porttree().dbapi.settings.lock() - return use, use_expand_hidden, usemask, useforce - - -def get_cleared_flags(cp): - ''' - .. versionadded:: 2015.8.0 - - Uses portage for compare use flags which is used for installing package - and use flags which now exist int /etc/portage/package.use/ - - @type cp: string - @param cp: eg cat/pkg - @rtype: tuple - @rparam: tuple with two lists - list of used flags and - list of flags which will be used - ''' - cpv = _get_cpv(cp) - final_use, use_expand_hidden, usemasked, useforced = get_all_cpv_use(cpv) - inst_flags = filter_flags(get_installed_use(cpv), use_expand_hidden, - usemasked, useforced) - final_flags = filter_flags(final_use, use_expand_hidden, - usemasked, useforced) - return inst_flags, final_flags - - -def is_changed_uses(cp): - ''' - .. versionadded:: 2015.8.0 - - Uses portage for determine if the use flags of installed package - is compatible with use flags in portage configs. - - @type cp: string - @param cp: eg cat/pkg - ''' - cpv = _get_cpv(cp) - i_flags, conf_flags = get_cleared_flags(cpv) - for i in i_flags: - try: - conf_flags.remove(i) - except ValueError: - return True - return True if conf_flags else False diff --git a/sls/_states/ini_manage.py b/sls/_states/ini_manage.py deleted file mode 100644 index d5c32e9..0000000 --- a/sls/_states/ini_manage.py +++ /dev/null @@ -1,284 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Manage ini files -================ - -:maintainer: -:maturity: new -:depends: re -:platform: all - -''' - -# Import Python libs - - -# Import Salt libs -import salt.ext.six as six - -__virtualname__ = 'ini' - - -def __virtual__(): - ''' - Only load if the ini module is available - ''' - return __virtualname__ if 'ini.set_option' in __salt__ else False - - -def options_present(name, sections=None, separator='=', strict=False): - ''' - .. code-block:: yaml - - /home/saltminion/api-paste.ini: - ini.options_present: - - separator: '=' - - strict: True - - sections: - test: - testkey: 'testval' - secondoption: 'secondvalue' - test1: - testkey1: 'testval121' - - options present in file and not specified in sections - dict will be untouched, unless `strict: True` flag is - used - - changes dict will contain the list of changes made - ''' - ret = {'name': name, - 'changes': {}, - 'result': True, - 'comment': 'No anomaly detected' - } - if __opts__['test']: - ret['result'] = True - ret['comment'] = '' - for section in sections or {}: - section_name = ' in section ' + section if section != 'DEFAULT_IMPLICIT' else '' - try: - cur_section = __salt__['ini.get_section'](name, section, separator) - except IOError as err: - ret['comment'] = "{0}".format(err) - ret['result'] = False - return ret - for key in sections[section]: - cur_value = cur_section.get(key) - if cur_value == str(sections[section][key]): - ret['comment'] += 'Key {0}{1} unchanged.\n'.format(key, section_name) - continue - ret['comment'] += 'Changed key {0}{1}.\n'.format(key, section_name) - ret['result'] = None - if ret['comment'] == '': - ret['comment'] = 'No changes detected.' - return ret - try: - changes = {} - if sections: - for section_name, section_body in sections.items(): - changes[section_name] = {} - if strict: - original = __salt__['ini.get_section'](name, section_name, separator) - for key_to_remove in set(original.keys()).difference(section_body.keys()): - orig_value = __salt__['ini.get_option'](name, section_name, key_to_remove, separator) - __salt__['ini.remove_option'](name, section_name, key_to_remove, separator) - changes[section_name].update({key_to_remove: ''}) - changes[section_name].update({key_to_remove: {'before': orig_value, - 'after': None}}) - options_updated = __salt__['ini.set_option'](name, {section_name: section_body}, separator) - if options_updated: - changes[section_name].update(options_updated[section_name]) - else: - changes = __salt__['ini.set_option'](name, sections, separator) - except IOError as err: - ret['comment'] = "{0}".format(err) - ret['result'] = False - return ret - if 'error' in changes: - ret['result'] = False - ret['comment'] = 'Errors encountered. {0}'.format(changes['error']) - ret['changes'] = {} - else: - if all(value == {} for value in changes.values()): - ret['changes'] = {} - ret['comment'] = 'No changes take effect' - else: - ret['changes'] = changes - ret['comment'] = 'Changes take effect' - return ret - - -def options_absent(name, sections=None, separator='='): - ''' - .. code-block:: yaml - - /home/saltminion/api-paste.ini: - ini.options_absent: - - separator: '=' - - sections: - test: - - testkey - - secondoption - test1: - - testkey1 - - options present in file and not specified in sections - dict will be untouched - - changes dict will contain the list of changes made - ''' - ret = {'name': name, - 'changes': {}, - 'result': True, - 'comment': 'No anomaly detected' - } - if __opts__['test']: - ret['result'] = True - ret['comment'] = '' - for section in sections or {}: - section_name = ' in section ' + section if section != 'DEFAULT_IMPLICIT' else '' - try: - cur_section = __salt__['ini.get_section'](name, section, separator) - except IOError as err: - ret['comment'] = "{0}".format(err) - ret['result'] = False - return ret - for key in sections[section]: - cur_value = cur_section.get(key) - if not cur_value: - ret['comment'] += 'Key {0}{1} does not exist.\n'.format(key, section_name) - continue - ret['comment'] += 'Deleted key {0}{1}.\n'.format(key, section_name) - ret['result'] = None - if ret['comment'] == '': - ret['comment'] = 'No changes detected.' - return ret - sections = sections or {} - for section, keys in six.iteritems(sections): - for key in keys: - try: - current_value = __salt__['ini.remove_option'](name, section, key, separator) - except IOError as err: - ret['comment'] = "{0}".format(err) - ret['result'] = False - return ret - if not current_value: - continue - if section not in ret['changes']: - ret['changes'].update({section: {}}) - ret['changes'][section].update({key: current_value}) - ret['comment'] = 'Changes take effect' - return ret - - -def sections_present(name, sections=None, separator='='): - ''' - .. code-block:: yaml - - /home/saltminion/api-paste.ini: - ini.sections_present: - - separator: '=' - - sections: - - section_one - - section_two - - This will only create empty sections. To also create options, use - options_present state - - options present in file and not specified in sections will be deleted - changes dict will contain the sections that changed - ''' - ret = {'name': name, - 'changes': {}, - 'result': True, - 'comment': 'No anomaly detected' - } - if __opts__['test']: - ret['result'] = True - ret['comment'] = '' - for section in sections or {}: - try: - cur_section = __salt__['ini.get_section'](name, section, separator) - except IOError as err: - ret['result'] = False - ret['comment'] = "{0}".format(err) - return ret - if cmp(dict(sections[section]), cur_section) == 0: - ret['comment'] += 'Section unchanged {0}.\n'.format(section) - continue - elif cur_section: - ret['comment'] += 'Changed existing section {0}.\n'.format(section) - else: - ret['comment'] += 'Created new section {0}.\n'.format(section) - ret['result'] = None - if ret['comment'] == '': - ret['comment'] = 'No changes detected.' - return ret - section_to_update = {} - for section_name in sections or []: - section_to_update.update({section_name: {}}) - try: - changes = __salt__['ini.set_option'](name, section_to_update, separator) - except IOError as err: - ret['result'] = False - ret['comment'] = "{0}".format(err) - return ret - if 'error' in changes: - ret['result'] = False - ret['changes'] = 'Errors encountered {0}'.format(changes['error']) - return ret - ret['changes'] = changes - ret['comment'] = 'Changes take effect' - return ret - - -def sections_absent(name, sections=None, separator='='): - ''' - .. code-block:: yaml - - /home/saltminion/api-paste.ini: - ini.sections_absent: - - separator: '=' - - sections: - - test - - test1 - - options present in file and not specified in sections will be deleted - changes dict will contain the sections that changed - ''' - ret = {'name': name, - 'changes': {}, - 'result': True, - 'comment': 'No anomaly detected' - } - if __opts__['test']: - ret['result'] = True - ret['comment'] = '' - for section in sections or []: - try: - cur_section = __salt__['ini.get_section'](name, section, separator) - except IOError as err: - ret['result'] = False - ret['comment'] = "{0}".format(err) - return ret - if not cur_section: - ret['comment'] += 'Section {0} does not exist.\n'.format(section) - continue - ret['comment'] += 'Deleted section {0}.\n'.format(section) - ret['result'] = None - if ret['comment'] == '': - ret['comment'] = 'No changes detected.' - return ret - for section in sections or []: - try: - cur_section = __salt__['ini.remove_section'](name, section, separator) - except IOError as err: - ret['result'] = False - ret['comment'] = "{0}".format(err) - return ret - if not cur_section: - continue - ret['changes'][section] = cur_section - ret['comment'] = 'Changes take effect' - return ret diff --git a/sls/_states/pkg.py b/sls/_states/pkg.py index 5b302f8..d8fd7cc 100644 --- a/sls/_states/pkg.py +++ b/sls/_states/pkg.py @@ -39,7 +39,7 @@ A more involved example involves pulling from a custom repository. - keyserver: keyserver.ubuntu.com logstash: - pkg.installed + pkg.installed: - fromrepo: ppa:wolfnet/logstash Multiple packages can also be installed with the use of the pkgs @@ -74,15 +74,16 @@ state module ''' # Import python libs - +from __future__ import absolute_import, print_function, unicode_literals import fnmatch import logging import os import re -# Import salt libs -import salt.utils +# Import Salt libs import salt.utils.pkg +import salt.utils.platform +import salt.utils.versions from salt.output import nested from salt.utils.functools import namespaced_function as _namespaced_function from salt.utils.odict import OrderedDict as _OrderedDict @@ -92,7 +93,7 @@ from salt.exceptions import ( from salt.modules.pkg_resource import _repack_pkgs # Import 3rd-party libs -import salt.ext.six as six +from salt.ext import six # pylint: disable=invalid-name _repack_pkgs = _namespaced_function(_repack_pkgs, globals()) @@ -105,6 +106,7 @@ if salt.utils.platform.is_windows(): import datetime import errno import time + from functools import cmp_to_key # pylint: disable=import-error # pylint: enable=unused-import from salt.modules.win_pkg import _get_package_info @@ -158,21 +160,68 @@ def _get_comparison_spec(pkgver): comparison operator was passed, the comparison is assumed to be an "equals" comparison, and "==" will be the operator returned. ''' - match = re.match('^~?([<>])?(=)?([^<>=]+?)(?:\[.+\])?$', pkgver) - if not match: - raise CommandExecutionError( - 'Invalid version specification \'{0}\'.'.format(pkgver) - ) - gt_lt, eq, verstr = match.groups() - oper = gt_lt or '' - oper += eq or '' - # A comparison operator of "=" is redundant, but possible. - # Change it to "==" so that the version comparison works + oper, verstr = salt.utils.pkg.split_comparison(pkgver.strip()) if oper in ('=', ''): oper = '==' return oper, verstr +def _parse_version_string(version_conditions_string): + ''' + Returns a list of two-tuples containing (operator, version). + ''' + result = [] + version_conditions_string = version_conditions_string.strip() + if not version_conditions_string: + return result + if __grains__.get('os') == 'Gentoo': + return [_get_comparison_spec(version_conditions_string)] + else: + for version_condition in version_conditions_string.split(','): + operator_and_version = _get_comparison_spec(version_condition) + result.append(operator_and_version) + return result + + +def _fulfills_version_string(installed_versions, version_conditions_string, ignore_epoch=False, allow_updates=False): + ''' + Returns True if any of the installed versions match the specified version conditions, + otherwise returns False. + + installed_versions + The installed versions + + version_conditions_string + The string containing all version conditions. E.G. + 1.2.3-4 + >=1.2.3-4 + >=1.2.3-4, <2.3.4-5 + >=1.2.3-4, <2.3.4-5, !=1.2.4-1 + + ignore_epoch : False + When a package version contains an non-zero epoch (e.g. + ``1:3.14.159-2.el7``, and a specific version of a package is desired, + set this option to ``True`` to ignore the epoch when comparing + versions. + + allow_updates : False + Allow the package to be updated outside Salt's control (e.g. auto updates on Windows). + This means a package on the Minion can have a newer version than the latest available in + the repository without enforcing a re-installation of the package. + (Only applicable if only one strict version condition is specified E.G. version: 2.0.6~ubuntu3) + ''' + version_conditions = _parse_version_string(version_conditions_string) + for installed_version in installed_versions: + fullfills_all = True + for operator, version_string in version_conditions: + if allow_updates and len(version_conditions) == 1 and operator == '==': + operator = '>=' + fullfills_all = fullfills_all and _fulfills_version_spec([installed_version], operator, version_string, ignore_epoch=ignore_epoch) + if fullfills_all: + return True + return False + + def _fulfills_version_spec(versions, oper, desired_version, ignore_epoch=False): ''' @@ -181,7 +230,7 @@ def _fulfills_version_spec(versions, oper, desired_version, ''' cmp_func = __salt__.get('pkg.version_cmp') # stripping "with_origin" dict wrapper - if salt.utils.is_freebsd(): + if salt.utils.platform.is_freebsd(): if isinstance(versions, dict) and 'version' in versions: versions = versions['version'] for ver in versions: @@ -189,10 +238,15 @@ def _fulfills_version_spec(versions, oper, desired_version, if fnmatch.fnmatch(ver, desired_version): return True - if salt.utils.compare_versions( + if '*' in desired_version: + if ver.startswith(desired_version[:desired_version.rfind('*')]): + return True + + if salt.utils.versions.compare( ver1=ver, oper=oper, ver2=desired_version, cmp_func=cmp_func, ignore_epoch=ignore_epoch): return True + return False @@ -305,15 +359,12 @@ def _find_download_targets(name=None, version_spec = True try: - oper, verstr = _get_comparison_spec(pkgver) + if not _fulfills_version_string(cver.keys(), pkgver, ignore_epoch=ignore_epoch): + targets[pkgname] = pkgver except CommandExecutionError as exc: problems.append(exc.strerror) continue - if not _fulfills_version_spec(cver.keys(), oper, verstr, - ignore_epoch=ignore_epoch): - targets[pkgname] = pkgver - if problems: return {'name': name, 'changes': {}, @@ -425,19 +476,16 @@ def _find_remove_targets(name=None, continue version_spec = True try: - oper, verstr = _get_comparison_spec(pkgver) + if _fulfills_version_string(cver, pkgver, ignore_epoch=ignore_epoch): + targets.append(pkgname) + else: + log.debug( + 'Current version (%s) did not match desired version ' + 'specification (%s), will not remove', cver, pkgver + ) except CommandExecutionError as exc: problems.append(exc.strerror) continue - if not _fulfills_version_spec(cver, oper, verstr, - ignore_epoch=ignore_epoch): - log.debug( - 'Current version ({0}) did not match desired version ' - 'specification ({1}), will not remove' - .format(cver, verstr) - ) - else: - targets.append(pkgname) if problems: return {'name': name, @@ -447,9 +495,8 @@ def _find_remove_targets(name=None, if not targets: # All specified packages are already absent - msg = ( - 'All specified packages{0} are already absent' - .format(' (matching specified versions)' if version_spec else '') + msg = 'All specified packages{0} are already absent'.format( + ' (matching specified versions)' if version_spec else '' ) return {'name': name, 'changes': {}, @@ -511,27 +558,29 @@ def _find_install_targets(name=None, if __grains__['os'] == 'FreeBSD': kwargs['with_origin'] = True - if salt.utils.is_windows(): + if salt.utils.platform.is_windows(): # Windows requires a refresh to establish a pkg db if refresh=True, so # add it to the kwargs. kwargs['refresh'] = refresh + resolve_capabilities = kwargs.get('resolve_capabilities', False) and 'pkg.list_provides' in __salt__ try: cur_pkgs = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs) + cur_prov = resolve_capabilities and __salt__['pkg.list_provides'](**kwargs) or dict() except CommandExecutionError as exc: return {'name': name, 'changes': {}, 'result': False, 'comment': exc.strerror} - if salt.utils.is_windows() and kwargs.pop('refresh', False): + if salt.utils.platform.is_windows() and kwargs.pop('refresh', False): # We already refreshed when we called pkg.list_pkgs was_refreshed = True refresh = False if any((pkgs, sources)): if pkgs: - desired = _repack_pkgs(pkgs) + desired = _repack_pkgs(pkgs, normalize=normalize) elif sources: desired = __salt__['pkg_resource.pack_sources']( sources, @@ -546,9 +595,10 @@ def _find_install_targets(name=None, 'comment': 'Invalidly formatted \'{0}\' parameter. See ' 'minion log.'.format('pkgs' if pkgs else 'sources')} + to_unpurge = _find_unpurge_targets(desired) else: - if salt.utils.is_windows(): + if salt.utils.platform.is_windows(): pkginfo = _get_package_info(name, saltenv=kwargs['saltenv']) if not pkginfo: return {'name': name, @@ -608,7 +658,7 @@ def _find_install_targets(name=None, not_installed = dict([ (name, version) for name, version in desired.items() - if not (name in cur_pkgs and version in (None, cur_pkgs[name])) + if not (name in cur_pkgs and (version is None or _fulfills_version_string(cur_pkgs[name], version))) ]) if not_installed: try: @@ -675,15 +725,18 @@ def _find_install_targets(name=None, problems = [] warnings = [] failed_verify = False - for key, val in six.iteritems(desired): - cver = cur_pkgs.get(key, []) + for package_name, version_string in six.iteritems(desired): + cver = cur_pkgs.get(package_name, []) + if resolve_capabilities and not cver and package_name in cur_prov: + cver = cur_pkgs.get(cur_prov.get(package_name)[0], []) + # Package not yet installed, so add to targets if not cver: - targets[key] = val + targets[package_name] = version_string continue if sources: if reinstall: - to_reinstall[key] = val + to_reinstall[package_name] = version_string continue elif 'lowpkg.bin_pkg_info' not in __salt__: continue @@ -691,36 +744,36 @@ def _find_install_targets(name=None, # package's name and version err = 'Unable to cache {0}: {1}' try: - cached_path = __salt__['cp.cache_file'](val, saltenv=kwargs['saltenv']) + cached_path = __salt__['cp.cache_file'](version_string, saltenv=kwargs['saltenv']) except CommandExecutionError as exc: - problems.append(err.format(val, exc)) + problems.append(err.format(version_string, exc)) continue if not cached_path: - problems.append(err.format(val, 'file not found')) + problems.append(err.format(version_string, 'file not found')) continue elif not os.path.exists(cached_path): - problems.append('{0} does not exist on minion'.format(val)) + problems.append('{0} does not exist on minion'.format(version_string)) continue source_info = __salt__['lowpkg.bin_pkg_info'](cached_path) if source_info is None: - warnings.append('Failed to parse metadata for {0}'.format(val)) + warnings.append('Failed to parse metadata for {0}'.format(version_string)) continue else: - oper = '==' verstr = source_info['version'] else: + verstr = version_string if reinstall: - to_reinstall[key] = val + to_reinstall[package_name] = version_string continue - if not __salt__['pkg_resource.check_extra_requirements'](key, val): - targets[key] = val + if not __salt__['pkg_resource.check_extra_requirements'](package_name, version_string): + targets[package_name] = version_string continue # No version specified and pkg is installed - elif __salt__['pkg_resource.version_clean'](val) is None: + elif __salt__['pkg_resource.version_clean'](version_string) is None: if (not reinstall) and pkg_verify: try: verify_result = __salt__['pkg.verify']( - key, + package_name, ignore_types=ignore_types, verify_options=verify_options ) @@ -728,43 +781,43 @@ def _find_install_targets(name=None, failed_verify = exc.strerror continue if verify_result: - to_reinstall[key] = val - altered_files[key] = verify_result - continue - try: - oper, verstr = _get_comparison_spec(val) - except CommandExecutionError as exc: - problems.append(exc.strerror) + to_reinstall[package_name] = version_string + altered_files[package_name] = verify_result continue + version_fulfilled = False + allow_updates = bool(not sources and kwargs.get('allow_updates')) + try: + version_fulfilled = _fulfills_version_string(cver, verstr, ignore_epoch=ignore_epoch, allow_updates=allow_updates) + except CommandExecutionError as exc: + problems.append(exc.strerror) + continue # Compare desired version against installed version. version_spec = True - if not sources and 'allow_updates' in kwargs: - if kwargs['allow_updates']: - oper = '>=' - if not _fulfills_version_spec(cver, oper, verstr, - ignore_epoch=ignore_epoch): + if not version_fulfilled: if reinstall: - to_reinstall[key] = val - elif pkg_verify and oper == '==': - try: - verify_result = __salt__['pkg.verify']( - key, - ignore_types=ignore_types, - verify_options=verify_options) - except (CommandExecutionError, SaltInvocationError) as exc: - failed_verify = exc.strerror - continue - if verify_result: - to_reinstall[key] = val - altered_files[key] = verify_result + to_reinstall[package_name] = version_string else: - log.debug( - 'Current version ({0}) did not match desired version ' - 'specification ({1}), adding to installation targets' - .format(cver, val) - ) - targets[key] = val + version_conditions = _parse_version_string(version_string) + if pkg_verify and any(oper == '==' for oper, version in version_conditions): + try: + verify_result = __salt__['pkg.verify']( + package_name, + ignore_types=ignore_types, + verify_options=verify_options) + except (CommandExecutionError, SaltInvocationError) as exc: + failed_verify = exc.strerror + continue + if verify_result: + to_reinstall[package_name] = version_string + altered_files[package_name] = verify_result + else: + log.debug( + 'Current version (%s) did not match desired version ' + 'specification (%s), adding to installation targets', + cver, version_string + ) + targets[package_name] = version_string if failed_verify: problems.append(failed_verify) @@ -794,13 +847,15 @@ def _find_install_targets(name=None, warnings, was_refreshed) -def _verify_install(desired, new_pkgs, ignore_epoch=False): +def _verify_install(desired, new_pkgs, ignore_epoch=False, new_caps=None): ''' Determine whether or not the installed packages match what was requested in the SLS file. ''' ok = [] failed = [] + if not new_caps: + new_caps = dict() for pkgname, pkgver in desired.items(): # FreeBSD pkg supports `openjdk` and `java/openjdk7` package names. # Homebrew for Mac OSX does something similar with tap names @@ -817,6 +872,8 @@ def _verify_install(desired, new_pkgs, ignore_epoch=False): cver = new_pkgs.get(pkgname.split('=')[0]) else: cver = new_pkgs.get(pkgname) + if not cver and pkgname in new_caps: + cver = new_pkgs.get(new_caps.get(pkgname)[0]) if not cver: log.warning('Desired package {0} is not installed'.format(pkgname)) @@ -832,11 +889,11 @@ def _verify_install(desired, new_pkgs, ignore_epoch=False): ok.append(pkgname) continue oper, verstr = _get_comparison_spec(pkgver) - if _fulfills_version_spec(cver, oper, verstr, - ignore_epoch=ignore_epoch): + if _fulfills_version_spec( + cver, oper, verstr, ignore_epoch=ignore_epoch): ok.append(pkgname) else: - log.warning('Package {0}-{1} does not fullfill {1}{2}'.\ + log.warning('Package {0}-{1} does not fullfill {2}{1}'.\ format(pkgname, cver, oper, verstr)) failed.append(pkgname) return ok, failed @@ -864,7 +921,8 @@ def _preflight_check(desired, fromrepo, **kwargs): return {} ret = {'suggest': {}, 'no_suggest': []} pkginfo = __salt__['pkg.check_db']( - *list(desired), fromrepo=fromrepo, **kwargs) + *list(desired.keys()), fromrepo=fromrepo, **kwargs + ) for pkgname in pkginfo: if pkginfo[pkgname]['found'] is False: if pkginfo[pkgname]['suggestions']: @@ -883,6 +941,26 @@ def _nested_output(obj): return ret +def _resolve_capabilities(pkgs, refresh=False, **kwargs): + ''' + Resolve capabilities in ``pkgs`` and exchange them with real package + names, when the result is distinct. + This feature can be turned on while setting the paramter + ``resolve_capabilities`` to True. + + Return the input dictionary with replaced capability names and as + second return value a bool which say if a refresh need to be run. + + In case of ``resolve_capabilities`` is False (disabled) or not + supported by the implementation the input is returned unchanged. + ''' + if not pkgs or 'pkg.resolve_capabilities' not in __salt__: + return pkgs, refresh + + ret = __salt__['pkg.resolve_capabilities'](pkgs, refresh=refresh, **kwargs) + return ret, False + + def installed( name, version=None, @@ -916,6 +994,7 @@ def installed( for the following pkg providers: :mod:`apt `, :mod:`ebuild `, :mod:`pacman `, + :mod:`pkgin `, :mod:`win_pkg `, :mod:`yumpkg `, and :mod:`zypper `. The version number includes the @@ -945,8 +1024,8 @@ def installed( In version 2015.8.9, an **ignore_epoch** argument has been added to :py:mod:`pkg.installed `, - :py:mod:`pkg.removed `, and - :py:mod:`pkg.purged ` states, which + :py:mod:`pkg.removed `, and + :py:mod:`pkg.purged ` states, which causes the epoch to be disregarded when the state checks to see if the desired version was installed. @@ -991,11 +1070,11 @@ def installed( **WILDCARD VERSIONS** As of the 2017.7.0 release, this state now supports wildcards in - package versions for SUSE SLES/Leap/Tumbleweed, Debian/Ubuntu, RHEL/CentOS, - Arch Linux, and their derivatives. Using wildcards can be useful for - packages where the release name is built into the version in some way, - such as for RHEL/CentOS which typically has version numbers like - ``1.2.34-5.el7``. An example of the usage for this would be: + package versions for SUSE SLES/Leap/Tumbleweed, Debian/Ubuntu, + RHEL/CentOS, Arch Linux, and their derivatives. Using wildcards can be + useful for packages where the release name is built into the version in + some way, such as for RHEL/CentOS which typically has version numbers + like ``1.2.34-5.el7``. An example of the usage for this would be: .. code-block:: yaml @@ -1003,6 +1082,11 @@ def installed( pkg.installed: - version: '1.2.34*' + Keep in mind that using wildcard versions will result in a slower state + run since Salt must gather the available versions of the specified + packages and figure out which of them match the specified wildcard + expression. + :param bool refresh: This parameter controls whether or not the package repo database is updated prior to installing the requested package(s). @@ -1114,6 +1198,11 @@ def installed( .. versionadded:: 2014.1.1 + :param bool resolve_capabilities: + Turn on resolving capabilities. This allow one to name "provides" or alias names for packages. + + .. versionadded:: 2018.3.0 + :param bool allow_updates: Allow the package to be updated outside Salt's control (e.g. auto updates on Windows). This means a package on the Minion can have a @@ -1266,8 +1355,11 @@ def installed( ``NOTE:`` For :mod:`apt `, :mod:`ebuild `, - :mod:`pacman `, :mod:`yumpkg `, - and :mod:`zypper `, version numbers can be specified + :mod:`pacman `, + :mod:`winrepo `, + :mod:`yumpkg `, and + :mod:`zypper `, + version numbers can be specified in the ``pkgs`` argument. For example: .. code-block:: yaml @@ -1280,9 +1372,11 @@ def installed( - baz Additionally, :mod:`ebuild `, :mod:`pacman - ` and :mod:`zypper ` support - the ``<``, ``<=``, ``>=``, and ``>`` operators for more control over - what versions will be installed. For example: + `, :mod:`zypper `, + :mod:`yum/dnf `, and :mod:`apt + ` support the ``<``, ``<=``, ``>=``, and ``>`` + operators for more control over what versions will be installed. For + example: .. code-block:: yaml @@ -1390,7 +1484,7 @@ def installed( If this parameter is set to True and the package is not already installed, the state will fail. - :param bool report_reboot_exit_codes: + :param bool report_reboot_exit_codes: If the installer exits with a recognized exit code indicating that a reboot is required, the module function @@ -1441,8 +1535,23 @@ def installed( 'result': True, 'comment': 'No packages to install provided'} + # If just a name (and optionally a version) is passed, just pack them into + # the pkgs argument. + if name and not any((pkgs, sources)): + if version: + pkgs = [{name: version}] + version = None + else: + pkgs = [name] + kwargs['saltenv'] = __env__ refresh = salt.utils.pkg.check_refresh(__opts__, refresh) + + # check if capabilities should be checked and modify the requested packages + # accordingly. + if pkgs: + pkgs, refresh = _resolve_capabilities(pkgs, refresh=refresh, **kwargs) + if not isinstance(pkg_verify, list): pkg_verify = pkg_verify is True if (pkg_verify or isinstance(pkg_verify, list)) \ @@ -1453,7 +1562,7 @@ def installed( 'comment': 'pkg.verify not implemented'} if not isinstance(version, six.string_types) and version is not None: - version = str(version) + version = six.text_type(version) kwargs['allow_updates'] = allow_updates @@ -1476,54 +1585,46 @@ def installed( # _find_install_targets() found no targets or encountered an error # check that the hold function is available - if 'pkg.hold' in __salt__: - if 'hold' in kwargs: - try: - if kwargs['hold']: - hold_ret = __salt__['pkg.hold']( - name=name, pkgs=pkgs, sources=sources - ) - else: - hold_ret = __salt__['pkg.unhold']( - name=name, pkgs=pkgs, sources=sources - ) - except (CommandExecutionError, SaltInvocationError) as exc: - return {'name': name, - 'changes': {}, - 'result': False, - 'comment': str(exc)} + if 'pkg.hold' in __salt__ and 'hold' in kwargs: + try: + action = 'pkg.hold' if kwargs['hold'] else 'pkg.unhold' + hold_ret = __salt__[action]( + name=name, pkgs=pkgs, sources=sources + ) + except (CommandExecutionError, SaltInvocationError) as exc: + return {'name': name, + 'changes': {}, + 'result': False, + 'comment': six.text_type(exc)} - if 'result' in hold_ret and not hold_ret['result']: - return {'name': name, - 'changes': {}, - 'result': False, - 'comment': 'An error was encountered while ' - 'holding/unholding package(s): {0}' - .format(hold_ret['comment'])} - else: - modified_hold = [hold_ret[x] for x in hold_ret - if hold_ret[x]['changes']] - not_modified_hold = [hold_ret[x] for x in hold_ret - if not hold_ret[x]['changes'] - and hold_ret[x]['result']] - failed_hold = [hold_ret[x] for x in hold_ret - if not hold_ret[x]['result']] + if 'result' in hold_ret and not hold_ret['result']: + return {'name': name, + 'changes': {}, + 'result': False, + 'comment': 'An error was encountered while ' + 'holding/unholding package(s): {0}' + .format(hold_ret['comment'])} + else: + modified_hold = [hold_ret[x] for x in hold_ret + if hold_ret[x]['changes']] + not_modified_hold = [hold_ret[x] for x in hold_ret + if not hold_ret[x]['changes'] + and hold_ret[x]['result']] + failed_hold = [hold_ret[x] for x in hold_ret + if not hold_ret[x]['result']] - if modified_hold: - for i in modified_hold: - result['comment'] += '.\n{0}'.format(i['comment']) - result['result'] = i['result'] - result['changes'][i['name']] = i['changes'] + for i in modified_hold: + result['comment'] += '.\n{0}'.format(i['comment']) + result['result'] = i['result'] + result['changes'][i['name']] = i['changes'] - if not_modified_hold: - for i in not_modified_hold: - result['comment'] += '.\n{0}'.format(i['comment']) - result['result'] = i['result'] + for i in not_modified_hold: + result['comment'] += '.\n{0}'.format(i['comment']) + result['result'] = i['result'] - if failed_hold: - for i in failed_hold: - result['comment'] += '.\n{0}'.format(i['comment']) - result['result'] = i['result'] + for i in failed_hold: + result['comment'] += '.\n{0}'.format(i['comment']) + result['result'] = i['result'] return result if to_unpurge and 'lowpkg.unpurge' not in __salt__: @@ -1542,9 +1643,9 @@ def installed( elif sources: oldsources = sources sources = [x for x in oldsources - if next(iter(list(x))) in targets] + if next(iter(list(x.keys()))) in targets] sources.extend([x for x in oldsources - if next(iter(list(x))) in to_reinstall]) + if next(iter(list(x.keys()))) in to_reinstall]) comment = [] if __opts__['test']: @@ -1603,14 +1704,10 @@ def installed( not_modified_hold = None failed_hold = None if targets or to_reinstall: - force = False - if salt.utils.is_freebsd(): - force = True # Downgrades need to be forced. try: - pkg_ret = __salt__['pkg.install'](name, + pkg_ret = __salt__['pkg.install'](name=None, refresh=refresh, version=version, - force=force, fromrepo=fromrepo, skip_verify=skip_verify, pkgs=pkgs, @@ -1618,6 +1715,7 @@ def installed( reinstall=bool(to_reinstall), normalize=normalize, update_holds=update_holds, + ignore_epoch=ignore_epoch, **kwargs) except CommandExecutionError as exc: ret = {'name': name, 'result': False} @@ -1647,45 +1745,40 @@ def installed( # checks reinstall targets works. pkg_ret = {} - if 'pkg.hold' in __salt__: - if 'hold' in kwargs: - try: - if kwargs['hold']: - hold_ret = __salt__['pkg.hold']( - name=name, pkgs=pkgs, sources=sources - ) - else: - hold_ret = __salt__['pkg.unhold']( - name=name, pkgs=pkgs, sources=sources - ) - except (CommandExecutionError, SaltInvocationError) as exc: - comment.append(str(exc)) - ret = {'name': name, - 'changes': changes, - 'result': False, - 'comment': '\n'.join(comment)} - if warnings: - ret.setdefault('warnings', []).extend(warnings) - return ret - else: - if 'result' in hold_ret and not hold_ret['result']: - ret = {'name': name, - 'changes': {}, - 'result': False, - 'comment': 'An error was encountered while ' - 'holding/unholding package(s): {0}' - .format(hold_ret['comment'])} - if warnings: - ret.setdefault('warnings', []).extend(warnings) - return ret - else: - modified_hold = [hold_ret[x] for x in hold_ret - if hold_ret[x]['changes']] - not_modified_hold = [hold_ret[x] for x in hold_ret - if not hold_ret[x]['changes'] - and hold_ret[x]['result']] - failed_hold = [hold_ret[x] for x in hold_ret - if not hold_ret[x]['result']] + if 'pkg.hold' in __salt__ and 'hold' in kwargs: + try: + action = 'pkg.hold' if kwargs['hold'] else 'pkg.unhold' + hold_ret = __salt__[action]( + name=name, pkgs=desired + ) + except (CommandExecutionError, SaltInvocationError) as exc: + comment.append(six.text_type(exc)) + ret = {'name': name, + 'changes': changes, + 'result': False, + 'comment': '\n'.join(comment)} + if warnings: + ret.setdefault('warnings', []).extend(warnings) + return ret + else: + if 'result' in hold_ret and not hold_ret['result']: + ret = {'name': name, + 'changes': {}, + 'result': False, + 'comment': 'An error was encountered while ' + 'holding/unholding package(s): {0}' + .format(hold_ret['comment'])} + if warnings: + ret.setdefault('warnings', []).extend(warnings) + return ret + else: + modified_hold = [hold_ret[x] for x in hold_ret + if hold_ret[x]['changes']] + not_modified_hold = [hold_ret[x] for x in hold_ret + if not hold_ret[x]['changes'] + and hold_ret[x]['result']] + failed_hold = [hold_ret[x] for x in hold_ret + if not hold_ret[x]['result']] if to_unpurge: changes['purge_desired'] = __salt__['lowpkg.unpurge'](*to_unpurge) @@ -1701,8 +1794,13 @@ def installed( if __grains__['os'] == 'FreeBSD': kwargs['with_origin'] = True new_pkgs = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs) + if kwargs.get('resolve_capabilities', False) and 'pkg.list_provides' in __salt__: + new_caps = __salt__['pkg.list_provides'](**kwargs) + else: + new_caps = {} ok, failed = _verify_install(desired, new_pkgs, - ignore_epoch=ignore_epoch) + ignore_epoch=ignore_epoch, + new_caps=new_caps) modified = [x for x in ok if x in targets] not_modified = [x for x in ok if x not in targets @@ -1743,6 +1841,10 @@ def installed( if len(changes[change_name]['old']) > 0: changes[change_name]['old'] += '\n' changes[change_name]['old'] += '{0}'.format(i['changes']['old']) + else: + comment.append(i['comment']) + changes[change_name] = {} + changes[change_name]['new'] = '{0}'.format(i['changes']['new']) # Any requested packages that were not targeted for install or reinstall if not_modified: @@ -1921,6 +2023,11 @@ def downloaded(name, - dos2unix - salt-minion: 2015.8.5-1.el6 + :param bool resolve_capabilities: + Turn on resolving capabilities. This allow one to name "provides" or alias names for packages. + + .. versionadded:: 2018.3.0 + CLI Example: .. code-block:: yaml @@ -1946,11 +2053,22 @@ def downloaded(name, ret['comment'] = 'No packages to download provided' return ret + # If just a name (and optionally a version) is passed, just pack them into + # the pkgs argument. + if name and not pkgs: + if version: + pkgs = [{name: version}] + version = None + else: + pkgs = [name] + # It doesn't make sense here to received 'downloadonly' as kwargs - # as we're explicitely passing 'downloadonly=True' to execution module. + # as we're explicitly passing 'downloadonly=True' to execution module. if 'downloadonly' in kwargs: del kwargs['downloadonly'] + pkgs, _refresh = _resolve_capabilities(pkgs, **kwargs) + # Only downloading not yet downloaded packages targets = _find_download_targets(name, version, @@ -2120,7 +2238,7 @@ def patch_downloaded(name, advisory_ids=None, **kwargs): 'this platform'} # It doesn't make sense here to received 'downloadonly' as kwargs - # as we're explicitely passing 'downloadonly=True' to execution module. + # as we're explicitly passing 'downloadonly=True' to execution module. if 'downloadonly' in kwargs: del kwargs['downloadonly'] return patch_installed(name=name, advisory_ids=advisory_ids, downloadonly=True, **kwargs) @@ -2197,6 +2315,10 @@ def latest( This parameter is available only on Debian based distributions and has no effect on the rest. + :param bool resolve_capabilities: + Turn on resolving capabilities. This allow one to name "provides" or alias names for packages. + + .. versionadded:: 2018.3.0 Multiple Package Installation Options: @@ -2272,16 +2394,15 @@ def latest( 'changes': {}, 'result': False, 'comment': 'The "sources" parameter is not supported.'} - if pkgs: - pkgs = _repack_pkgs(pkgs) - if not pkgs: + elif pkgs: + desired_pkgs = list(_repack_pkgs(pkgs).keys()) + if not desired_pkgs: # Badly-formatted SLS return {'name': name, 'changes': {}, 'result': False, 'comment': 'Invalidly formatted "pkgs" parameter. See ' 'minion log.'} - desired_pkgs = list(pkgs) else: if isinstance(pkgs, list) and len(pkgs) == 0: return { @@ -2291,11 +2412,14 @@ def latest( 'comment': 'No packages to install provided' } else: - pkgs = {name: None} desired_pkgs = [name] kwargs['saltenv'] = __env__ + # check if capabilities should be checked and modify the requested packages + # accordingly. + desired_pkgs, refresh = _resolve_capabilities(desired_pkgs, refresh=refresh, **kwargs) + try: avail = __salt__['pkg.latest_version'](*desired_pkgs, fromrepo=fromrepo, @@ -2325,22 +2449,24 @@ def latest( targets = {} problems = [] - for pkg, version in pkgs.items(): - if cur[pkg]: - if _fulfills_version_spec([cur[pkg]], '!=', avail[pkg]): - targets[pkg] = avail[pkg] - else: - if version: - target, flag_changes = __salt__['pkg.process_target'](pkg, version) - if not cur[pkg] or (__grains__.get('os') == 'Gentoo' - and __salt__['portage_config.is_changed_uses'](pkg)): - targets[pkg] = avail[pkg] - elif avail[pkg]: - targets[pkg] = avail[pkg] + for pkg in desired_pkgs: + if not avail.get(pkg): + # Package either a) is up-to-date, or b) does not exist + if not cur.get(pkg): + # Package does not exist + msg = 'No information found for \'{0}\'.'.format(pkg) + log.error(msg) + problems.append(msg) + elif watch_flags \ + and __grains__.get('os') == 'Gentoo' \ + and __salt__['portage_config.is_changed_uses'](pkg): + # Package is up-to-date, but Gentoo USE flags are changing so + # we need to add it to the targets + targets[pkg] = cur[pkg] else: - msg = 'No information found for {0!r}.'.format(pkg) - log.error(msg) - problems.append(msg) + # Package either a) is not installed, or b) is installed and has an + # upgrade available + targets[pkg] = avail[pkg] if problems: return { @@ -2351,6 +2477,7 @@ def latest( } if targets: + # Find up-to-date packages # Find up-to-date packages up_to_date = [] if pkgs: @@ -2388,21 +2515,26 @@ def latest( 'result': None, 'comment': '\n'.join(comments)} - # Build updated list of pkgs to exclude non-targeted ones - targeted_pkgs = list(targets) if pkgs else None + if salt.utils.platform.is_windows(): + # pkg.install execution module on windows ensures the software + # package is installed when no version is specified, it does not + # upgrade the software to the latest. This is per the design. + # Build updated list of pkgs *with verion number*, exclude + # non-targeted ones + targeted_pkgs = [{x: targets[x]} for x in targets] + else: + # Build updated list of pkgs to exclude non-targeted ones + targeted_pkgs = list(targets) + # No need to refresh, if a refresh was necessary it would have been + # performed above when pkg.latest_version was run. try: - # No need to refresh, if a refresh was necessary it would have been - # performed above when pkg.latest_version was run. - changes = {} - if 'flag_changes' in locals(): - changes.update(flag_changes) - changes.update(__salt__['pkg.install'](name, + changes = __salt__['pkg.install'](name=None, refresh=False, fromrepo=fromrepo, skip_verify=skip_verify, pkgs=targeted_pkgs, - **kwargs)) + **kwargs) except CommandExecutionError as exc: return {'name': name, 'changes': {}, @@ -2415,7 +2547,7 @@ def latest( failed = [x for x in targets if not changes.get(x) or changes[x].get('new') != targets[x] and - targets[x] and targets[x] != 'latest'] + targets[x] != 'latest'] successful = [x for x in targets if x not in failed] comments = [] @@ -2451,7 +2583,7 @@ def latest( .format(', '.join(sorted(targets)))) else: comment = 'Package {0} failed to ' \ - 'update.'.format(next(iter(list(targets)))) + 'update.'.format(next(iter(list(targets.keys())))) if up_to_date: if len(up_to_date) <= 10: comment += ' The following packages were already ' \ @@ -2545,7 +2677,17 @@ def _uninstall( changes = __salt__['pkg.{0}'.format(action)](name, pkgs=pkgs, version=version, **kwargs) new = __salt__['pkg.list_pkgs'](versions_as_list=True, **kwargs) - failed = [x for x in pkg_params if x in new] + failed = [] + for x in pkg_params: + if __grains__['os_family'] in ['Suse', 'RedHat']: + # Check if the package version set to be removed is actually removed: + if x in new and not pkg_params[x]: + failed.append(x) + elif x in new and pkg_params[x] in new[x]: + failed.append(x + "-" + pkg_params[x]) + elif x in new: + failed.append(x) + if action == 'purge': new_removed = __salt__['pkg.list_pkgs'](versions_as_list=True, removed=True, @@ -2603,13 +2745,13 @@ def removed(name, .. code-block:: yaml vim-enhanced: - pkg.installed: + pkg.removed: - version: 2:7.4.160-1.el7 In version 2015.8.9, an **ignore_epoch** argument has been added to :py:mod:`pkg.installed `, - :py:mod:`pkg.removed `, and - :py:mod:`pkg.purged ` states, which + :py:mod:`pkg.removed `, and + :py:mod:`pkg.purged ` states, which causes the epoch to be disregarded when the state checks to see if the desired version was installed. If **ignore_epoch** was not set to ``True``, and instead of ``2:7.4.160-1.el7`` a version of @@ -2709,13 +2851,13 @@ def purged(name, .. code-block:: yaml vim-enhanced: - pkg.installed: + pkg.purged: - version: 2:7.4.160-1.el7 In version 2015.8.9, an **ignore_epoch** argument has been added to :py:mod:`pkg.installed `, - :py:mod:`pkg.removed `, and - :py:mod:`pkg.purged ` states, which + :py:mod:`pkg.removed `, and + :py:mod:`pkg.purged ` states, which causes the epoch to be disregarded when the state checks to see if the desired version was installed. If **ignore_epoch** was not set to ``True``, and instead of ``2:7.4.160-1.el7`` a version of @@ -2791,6 +2933,9 @@ def purged(name, def uptodate(name, refresh=False, pkgs=None, **kwargs): ''' .. versionadded:: 2014.7.0 + .. versionchanged:: 2018.3.0 + + Added support for the ``pkgin`` provider. Verify that the system is completely up to date. @@ -2817,6 +2962,11 @@ def uptodate(name, refresh=False, pkgs=None, **kwargs): This parameter available only on Debian based distributions, and have no effect on the rest. + :param bool resolve_capabilities: + Turn on resolving capabilities. This allow one to name "provides" or alias names for packages. + + .. versionadded:: 2018.3.0 + kwargs Any keyword arguments to pass through to ``pkg.upgrade``. @@ -2837,12 +2987,16 @@ def uptodate(name, refresh=False, pkgs=None, **kwargs): return ret if isinstance(refresh, bool): + pkgs, refresh = _resolve_capabilities(pkgs, refresh=refresh, **kwargs) try: packages = __salt__['pkg.list_upgrades'](refresh=refresh, **kwargs) + expected = {pkgname: {'new': pkgver, 'old': __salt__['pkg.version'](pkgname)} + for pkgname, pkgver in six.iteritems(packages)} if isinstance(pkgs, list): packages = [pkg for pkg in packages if pkg in pkgs] + expected = {pkgname: pkgver for pkgname, pkgver in six.iteritems(expected) if pkgname in pkgs} except Exception as exc: - ret['comment'] = str(exc) + ret['comment'] = six.text_type(exc) return ret else: ret['comment'] = 'refresh must be either True or False' @@ -2854,6 +3008,7 @@ def uptodate(name, refresh=False, pkgs=None, **kwargs): return ret elif __opts__['test']: ret['comment'] = 'System update will be performed' + ret['changes'] = expected ret['result'] = None return ret @@ -2870,8 +3025,17 @@ def uptodate(name, refresh=False, pkgs=None, **kwargs): 'packages: {0}'.format(exc)) return ret - ret['comment'] = 'Upgrade ran successfully' - ret['result'] = True + # If a package list was provided, ensure those packages were updated + missing = [] + if isinstance(pkgs, list): + missing = [pkg for pkg in six.iterkeys(expected) if pkg not in ret['changes']] + + if missing: + ret['comment'] = 'The following package(s) failed to update: {0}'.format(', '.join(missing)) + ret['result'] = False + else: + ret['comment'] = 'Upgrade ran successfully' + ret['result'] = True return ret @@ -2938,7 +3102,7 @@ def group_installed(name, skip=None, include=None, **kwargs): return ret for idx, item in enumerate(skip): if not isinstance(item, six.string_types): - skip[idx] = str(item) + skip[idx] = six.text_type(item) if include is None: include = [] @@ -2948,7 +3112,7 @@ def group_installed(name, skip=None, include=None, **kwargs): return ret for idx, item in enumerate(include): if not isinstance(item, six.string_types): - include[idx] = str(item) + include[idx] = six.text_type(item) try: diff = __salt__['pkg.group_diff'](name) @@ -3069,7 +3233,7 @@ def mod_aggregate(low, chunks, running): if low.get('fun') not in agg_enabled: return low for chunk in chunks: - tag = salt.utils.gen_state_tag(chunk) + tag = __utils__['state.gen_tag'](chunk) if tag in running: # Already ran the pkg state, skip aggregation continue @@ -3116,6 +3280,12 @@ def mod_aggregate(low, chunks, running): def mod_watch(name, **kwargs): ''' Install/reinstall a package based on a watch requisite + + .. note:: + This state exists to support special handling of the ``watch`` + :ref:`requisite `. It should not be called directly. + + Parameters for this function should be set by the state being triggered. ''' sfun = kwargs.pop('sfun', None) mapfun = {'purged': purged, diff --git a/sls/salt/repos.sls b/sls/salt/repos.sls index 3f1374a..8023483 100644 --- a/sls/salt/repos.sls +++ b/sls/salt/repos.sls @@ -28,9 +28,6 @@ include: {% set main_reponame = salt['pillar.get']('salt:repos:main:name', 'local') %} {% set main_remote_uri = salt['pillar.get']('salt:repos:main:remote') %} -{% set common_reponame = salt['pillar.get']('salt:repos:common:name', 'common') %} -{% set common_remote_uri = salt['pillar.get']('salt:repos:common:remote', - "git+ssh://git@git.bakka.su/salt-common.git") %} {% set extra_repos = salt['pillar.get']('salt:repos:extra', {} ) %} {% set extra_reponames = extra_repos|list %} @@ -122,26 +119,6 @@ salt-repo-{{ reponame }}-{{ branch }}: {% endif %} {% endfor %} -/var/salt/{{ common_reponame }}: - file.directory: - - user: root - - group: root - - file_mode: 644 - - dir_mode: 755 - git.latest: - - name: {{ common_remote_uri }} - - target: /var/salt/{{ common_reponame }} - - rev: master - - force_clone: True - - force_checkout: True - - force_fetch: True - - force_reset: True - - identity: /var/salt/ssh/salt - - require: - - file: /var/salt/ssh/salt - - require_in: - - file: /etc/salt/master.d/roots.conf - salt-roots-restart: service.running: - name: {{ 'salt-minion' if salt['grains.get']('salt:masterless', False) else 'salt-master' }} diff --git a/sls/salt/roots.sls b/sls/salt/roots.sls index d531348..cd315c4 100644 --- a/sls/salt/roots.sls +++ b/sls/salt/roots.sls @@ -6,7 +6,6 @@ from glob import glob d_salt = '/var/salt' main_reponame = __salt__['pillar.get']('salt:repos:main:name', 'local') -common_reponame = __salt__['pillar.get']('salt:repos:common:name', 'common') extra_repos = __salt__['pillar.get']('salt:repos:extra', {}) branches = filter(lambda x: x not in (main_reponame, '.git', '_mirror'), map(path.basename, @@ -19,20 +18,23 @@ for branch in branches: env_name = 'base' else: env_name = branch - content['file_roots'][env_name] = ( - [path.join(d_salt, main_reponame, branch, 'sls'), - path.join(d_salt, 'private', 'files'), - path.join(d_salt, common_reponame, 'sls')] + - filter(path.isdir, [path.join(d_salt, repo_name, extra_repos[repo_name].get('branch', branch), 'sls') - for repo_name in extra_repos.keys()])) - content['pillar_roots'][env_name] = ( - [path.join(d_salt, main_reponame, branch, 'pillar'), - path.join(d_salt, 'private', 'pillar'), - path.join(d_salt, common_reponame, 'pillar')] + - filter(path.isdir, [path.join(d_salt, repo_name, extra_repos[repo_name].get('branch', branch), 'pillar') - for repo_name in extra_repos.keys()])) + content['file_roots'][env_name] = [path.join(d_salt, main_reponame, branch, 'sls')] + content['pillar_roots'][env_name] = [path.join(d_salt, main_reponame, branch, 'pillar')] + for repo_name, data in extra_repos.items(): + if 'branch' in data: + content['file_roots'][env_name].append(path.join(d_salt, repo_name, data['branch'], 'sls')) + content['pillar_roots'][env_name].append(path.join(d_salt, repo_name, data['branch'], 'pillar')) + else: + if (path.isdir(path.join(d_salt, repo_name, branch, 'sls')) + and path.isdir(path.join(d_salt, repo_name, branch, 'pillar'))): + content['file_roots'][env_name].append(path.join(d_salt, repo_name, branch, 'sls')) + content['pillar_roots'][env_name].append(path.join(d_salt, repo_name, branch, 'pillar')) + elif 'default-branch' in data: + content['file_roots'][env_name].append(path.join(d_salt, repo_name, data['default-branch'], 'sls')) + content['pillar_roots'][env_name].append(path.join(d_salt, repo_name, data['default-branch'], 'pillar')) -content['pillar_roots']['base'].append(path.join(d_salt, 'private', 'pillar')) + content['file_roots'][env_name].append(path.join(d_salt, 'private', 'files')) + content['pillar_roots'][env_name].append(path.join(d_salt, 'private', 'pillar')) state('/etc/salt/master.d/roots.conf').file.managed( mode='644', user='root',