Merge remote-tracking branch 'upstream/develop' into unify-api

This commit is contained in:
Samuel M Smith 2013-08-08 16:33:20 -06:00
commit a841b5f452
33 changed files with 508 additions and 556 deletions

View File

@ -167,16 +167,20 @@
# - cmd
# The external auth system uses the Salt auth modules to authenticate and
# validate users to access areas of the Salt system
# validate users to access areas of the Salt system.
#
# external_auth:
# pam:
# fred:
# - test.*
#
# Time (in seconds) for a newly generated token to live. Default: 12 hours
# token_expire: 43200
# Allow minions to push files to the master. This is disabled by default, for
# security purposes.
# file_recv: False
##### Master Module Management #####
##########################################

View File

@ -11941,7 +11941,7 @@ salt \(aq*\(aq cp.is_cached salt://path/to/file
.UNINDENT
.INDENT 0.0
.TP
.B salt.modules.cp.list_master(env=\(aqbase\(aq)
.B salt.modules.cp.list_master(env=\(aqbase\(aq, prefix=\(aq\(aq)
List all of the files stored on the master
.sp
CLI Example:
@ -11954,7 +11954,7 @@ salt \(aq*\(aq cp.list_master
.UNINDENT
.INDENT 0.0
.TP
.B salt.modules.cp.list_master_dirs(env=\(aqbase\(aq)
.B salt.modules.cp.list_master_dirs(env=\(aqbase\(aq, prefix=\(aq\(aq)
List all of the directories stored on the master
.sp
CLI Example:

View File

@ -305,7 +305,7 @@ insecure!
``client_acl``
--------------
Default: {}
Default: ``{}``
Enable user accounts on the master to execute specific modules. These modules
can be expressed as regular expressions
@ -317,6 +317,74 @@ can be expressed as regular expressions
- test.ping
- pkg.*
.. conf_master:: client_acl_blacklist
``client_acl_blacklist``
------------------------
Default: ``{}``
Blacklist users or modules
This example would blacklist all non sudo users, including root from
running any commands. It would also blacklist any use of the "cmd"
module.
This is completely disabled by default.
.. code-block:: yaml
client_acl_blacklist:
users:
- root
- '^(?!sudo_).*$' # all non sudo users
modules:
- cmd
.. conf_master:: external_auth
``external_auth``
-----------------
Default: ``{}``
The external auth system uses the Salt auth modules to authenticate and
validate users to access areas of the Salt system.
.. code-block:: yaml
external_auth:
pam:
fred:
- test.*
.. conf_master:: token_expire
``token_expire``
----------------
Default: ``43200``
Time (in seconds) for a newly generated token to live. Default: 12 hours
.. code-block:: yaml
token_expire: 43200
.. conf_master:: file_recv
``file_recv``
-------------
Default: ``False``
Allow minions to push files to the master. This is disabled by default, for
security purposes.
.. code-block:: yaml
file_recv: False
Master Module Management
------------------------

View File

@ -110,3 +110,9 @@ To execute the manage.up runner:
.. code-block:: bash
# salt-call publish.runner manage.up
To match minions using other matchers, use ``expr_form``:
.. code-block:: bash
# salt-call publish.publish 'webserv* and not G@os:Ubuntu' test.ping expr_form='compound'

View File

@ -204,3 +204,16 @@ class Resolver(object):
except (IOError, OSError):
pass
return tdata
def request_token(self, load):
'''
Request a token fromt he master
'''
load['cmd'] = 'mk_token'
sreq = salt.payload.SREQ(
'tcp://{0[interface]}:{0[ret_port]}'.format(self.opts),
)
tdata = sreq.send('clear', load)
if 'token' not in tdata:
return tdata
return tdata

View File

@ -129,6 +129,7 @@ VALID_OPTS = {
'client_acl_blacklist': dict,
'external_auth': dict,
'token_expire': int,
'file_recv': bool,
'file_ignore_regex': bool,
'file_ignore_glob': bool,
'fileserver_backend': list,
@ -271,6 +272,7 @@ DEFAULT_MASTER_OPTS = {
'client_acl_blacklist': {},
'external_auth': {},
'token_expire': 43200,
'file_recv': False,
'file_buffer_size': 1048576,
'file_ignore_regex': None,
'file_ignore_glob': None,

View File

@ -102,7 +102,7 @@ class Client(object):
'''
raise NotImplementedError
def file_list_emptydirs(self, env='base'):
def file_list_emptydirs(self, env='base', prefix=''):
'''
List the empty dirs
'''
@ -207,13 +207,13 @@ class Client(object):
ldest = self._file_local_list(localfilesdest)
return sorted(fdest.union(ldest))
def file_list(self, env='base'):
def file_list(self, env='base', prefix=''):
'''
This function must be overwritten
'''
return []
def dir_list(self, env='base'):
def dir_list(self, env='base', prefix=''):
'''
This function must be overwritten
'''
@ -461,47 +461,56 @@ class LocalClient(Client):
return ''
return fnd['path']
def file_list(self, env='base'):
def file_list(self, env='base', prefix=''):
'''
Return a list of files in the given environment
with optional relative prefix path to limit directory traversal
'''
ret = []
if env not in self.opts['file_roots']:
return ret
for path in self.opts['file_roots'][env]:
if prefix:
path = os.path.join(path, prefix)
for root, dirs, files in os.walk(path, followlinks=True):
for fname in files:
ret.append(
os.path.relpath(
os.path.join(root, fname),
os.path.join(root, prefix, fname),
path
)
)
return ret
def file_list_emptydirs(self, env='base'):
def file_list_emptydirs(self, env='base', prefix=''):
'''
List the empty dirs in the file_roots
with optional relative prefix path to limit directory traversal
'''
ret = []
if env not in self.opts['file_roots']:
return ret
for path in self.opts['file_roots'][env]:
if prefix:
path = os.path.join(path, prefix)
for root, dirs, files in os.walk(path, followlinks=True):
if len(dirs) == 0 and len(files) == 0:
ret.append(os.path.relpath(root, path))
ret.append(os.path.relpath(root, os.path.join(prefix, path)))
return ret
def dir_list(self, env='base'):
def dir_list(self, env='base', prefix=''):
'''
List the dirs in the file_roots
with optional relative prefix path to limit directory traversal
'''
ret = []
if env not in self.opts['file_roots']:
return ret
for path in self.opts['file_roots'][env]:
if prefix:
path = os.path.join(path, prefix)
for root, dirs, files in os.walk(path, followlinks=True):
ret.append(os.path.relpath(root, path))
ret.append(os.path.relpath(root, os.path.join(prefix, path)))
return ret
def hash_file(self, path, env='base'):
@ -667,11 +676,12 @@ class RemoteClient(Client):
fn_.close()
return dest
def file_list(self, env='base'):
def file_list(self, env='base', prefix=''):
'''
List the files on the master
'''
load = {'env': env,
'prefix': prefix,
'cmd': '_file_list'}
try:
return self.auth.crypticle.loads(
@ -683,11 +693,12 @@ class RemoteClient(Client):
except SaltReqTimeoutError:
return ''
def file_list_emptydirs(self, env='base'):
def file_list_emptydirs(self, env='base', prefix=''):
'''
List the empty dirs on the master
'''
load = {'env': env,
'prefix': prefix,
'cmd': '_file_list_emptydirs'}
try:
return self.auth.crypticle.loads(
@ -699,11 +710,12 @@ class RemoteClient(Client):
except SaltReqTimeoutError:
return ''
def dir_list(self, env='base'):
def dir_list(self, env='base', prefix=''):
'''
List the dirs on the master
'''
load = {'env': env,
'prefix': prefix,
'cmd': '_dir_list'}
try:
return self.auth.crypticle.loads(

View File

@ -99,10 +99,11 @@ def file_list(load):
return ret
for path in __opts__['file_roots'][load['env']]:
path = os.path.join(path, load['prefix'])
for root, dirs, files in os.walk(path, followlinks=True):
for fname in files:
rel_fn = os.path.relpath(
os.path.join(root, fname),
os.path.join(root, load['prefix'], fname),
path
)
if not salt.fileserver.is_file_ignored(__opts__, rel_fn):
@ -118,11 +119,12 @@ def file_list_emptydirs(load):
if load['env'] not in __opts__['file_roots']:
return ret
for path in __opts__['file_roots'][load['env']]:
path = os.path.join(path, load['prefix'])
for root, dirs, files in os.walk(path, followlinks=True):
if len(dirs) == 0 and len(files) == 0:
rel_fn = os.path.relpath(root, path)
if not salt.fileserver.is_file_ignored(__opts__, rel_fn):
ret.append(rel_fn)
ret.append(os.path.join(load['prefix'], rel_fn))
return ret
@ -134,6 +136,7 @@ def dir_list(load):
if load['env'] not in __opts__['file_roots']:
return ret
for path in __opts__['file_roots'][load['env']]:
path = os.path.join(path, load['prefix'])
for root, dirs, files in os.walk(path, followlinks=True):
ret.append(os.path.relpath(root, path))
ret.append(os.path.relpath(root, os.path.join(load['prefix'], path)))
return ret

View File

@ -743,10 +743,10 @@ class AESFuncs(object):
# The minion is not who it says it is!
# We don't want to listen to it!
log.warn(
('Minion id {0} is not who it says it is and is attempting '
'to issue a peer command').format(
clear_load['id']
)
(
'Minion id {0} is not who it says it is and is attempting '
'to issue a peer command'
).format(clear_load['id'])
)
return False
perms = []
@ -1324,10 +1324,10 @@ class AESFuncs(object):
# The minion is not who it says it is!
# We don't want to listen to it!
log.warn(
('Minion id {0} is not who it says it is and is attempting '
'to revoke the key for {0}').format(
load['id']
)
(
'Minion id {0} is not who it says it is and is attempting '
'to revoke the key for {0}'
).format(load['id'])
)
return False
keyapi = salt.key.Key(self.opts)
@ -1753,6 +1753,94 @@ class ClearFuncs(object):
self.event.fire_event(eload, 'auth')
return ret
def runner(self, clear_load):
'''
Send a master control function back to the wheel system
'''
# All wheel ops pass through eauth
if 'token' in clear_load:
try:
token = self.loadauth.get_tok(clear_load['token'])
except Exception as exc:
log.error(
'Exception occurred when generating auth token: {0}'.format(
exc
)
)
return ''
if not token:
log.warning('Authentication failure of type "token" occurred.')
return ''
if token['eauth'] not in self.opts['external_auth']:
log.warning('Authentication failure of type "token" occurred.')
return ''
if token['name'] not in self.opts['external_auth'][token['eauth']]:
log.warning('Authentication failure of type "token" occurred.')
return ''
good = self.ckminions.runner_check(
self.opts['external_auth'][clear_load['eauth']][token['name']] if token['name'] in self.opts['external_auth'][clear_load['eauth']] else self.opts['external_auth'][token['eauth']]['*'],
clear_load['fun'])
if not good:
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(clear_load.get('username', 'UNKNOWN'))
log.warning(msg)
return ''
try:
fun = clear_load.pop('fun')
return self.wheel_.call_func(fun, **clear_load)
except Exception as exc:
log.error('Exception occurred while '
'introspecting {0}: {1}'.format(fun, exc))
return ''
if 'eauth' not in clear_load:
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(clear_load.get('username', 'UNKNOWN'))
log.warning(msg)
return ''
if clear_load['eauth'] not in self.opts['external_auth']:
# The eauth system is not enabled, fail
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(clear_load.get('username', 'UNKNOWN'))
log.warning(msg)
return ''
try:
name = self.loadauth.load_name(clear_load)
if not ((name in self.opts['external_auth'][clear_load['eauth']]) | ('*' in self.opts['external_auth'][clear_load['eauth']])):
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(clear_load.get('username', 'UNKNOWN'))
log.warning(msg)
return ''
if not self.loadauth.time_auth(clear_load):
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(clear_load.get('username', 'UNKNOWN'))
log.warning(msg)
return ''
good = self.ckminions.runner_check(
self.opts['external_auth'][clear_load['eauth']][name] if name in self.opts['external_auth'][clear_load['eauth']] else self.opts['external_auth'][token['eauth']]['*'],
clear_load['fun'])
if not good:
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(clear_load.get('username', 'UNKNOWN'))
log.warning(msg)
return ''
try:
fun = clear_load.pop('fun')
return self.wheel_.call_func(fun, **clear_load)
except Exception as exc:
log.error('Exception occurred while '
'introspecting {0}: {1}'.format(fun, exc))
return ''
except Exception as exc:
log.error(
'Exception occurred in the wheel system: {0}'.format(exc)
)
return ''
def wheel(self, clear_load):
'''
Send a master control function back to the wheel system
@ -1777,6 +1865,14 @@ class ClearFuncs(object):
if token['name'] not in self.opts['external_auth'][token['eauth']]:
log.warning('Authentication failure of type "token" occurred.')
return ''
good = self.ckminions.wheel_check(
self.opts['external_auth'][clear_load['eauth']][token['name']] if token['name'] in self.opts['external_auth'][clear_load['eauth']] else self.opts['external_auth'][token['eauth']]['*'],
clear_load['fun'])
if not good:
msg = ('Authentication failure of type "eauth" occurred for '
'user {0}.').format(clear_load.get('username', 'UNKNOWN'))
log.warning(msg)
return ''
try:
fun = clear_load.pop('fun')
@ -2060,7 +2156,7 @@ class ClearFuncs(object):
self.opts['hash_type']
)
# Announce the job on the event bus
# Announce the job on the event bus
self.event.fire_event(clear_load, 'new_job')
# Verify the jid dir

View File

@ -150,8 +150,13 @@ def latest_version(*names, **kwargs):
# If there are no installed versions that are greater than or equal
# to the install candidate, then the candidate is an upgrade, so
# add it to the return dict
if not any([compare(pkg1=x, oper='>=', pkg2=candidate)
for x in installed]):
if not any(
(salt.utils.compare_versions(ver1=x,
oper='>=',
ver2=candidate,
cmp_func=version_cmp)
for x in installed)
):
ret[name] = candidate
# Return a string if only one package name passed
@ -629,7 +634,7 @@ def upgrade_available(name):
return latest_version(name) != ''
def perform_cmp(pkg1='', pkg2=''):
def version_cmp(pkg1, pkg2):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
@ -637,8 +642,7 @@ def perform_cmp(pkg1='', pkg2=''):
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0ubuntu1' '0.2.4.1-0ubuntu1'
salt '*' pkg.perform_cmp pkg1='0.2.4-0ubuntu1' pkg2='0.2.4.1-0ubuntu1'
salt '*' pkg.version_cmp '0.2.4-0ubuntu1' '0.2.4.1-0ubuntu1'
'''
try:
for oper, ret in (('lt', -1), ('eq', 0), ('gt', 1)):
@ -651,18 +655,6 @@ def perform_cmp(pkg1='', pkg2=''):
return None
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)
def _split_repo_str(repo):
split = sourceslist.SourceEntry(repo)
return split.type, split.uri, split.dist, split.comps

View File

@ -278,29 +278,3 @@ def upgrade_available(pkg):
salt '*' pkg.upgrade_available <package name>
'''
return pkg in list_upgrades()
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)

View File

@ -312,7 +312,7 @@ def list_states(env='base'):
return __context__['cp.fileclient'].list_states(env)
def list_master(env='base'):
def list_master(env='base', prefix=''):
'''
List all of the files stored on the master
@ -321,10 +321,10 @@ def list_master(env='base'):
salt '*' cp.list_master
'''
_mk_client()
return __context__['cp.fileclient'].file_list(env)
return __context__['cp.fileclient'].file_list(env, prefix)
def list_master_dirs(env='base'):
def list_master_dirs(env='base', prefix=''):
'''
List all of the directories stored on the master
@ -333,7 +333,7 @@ def list_master_dirs(env='base'):
salt '*' cp.list_master_dirs
'''
_mk_client()
return __context__['cp.fileclient'].dir_list(env)
return __context__['cp.fileclient'].dir_list(env, prefix)
def list_minion(env='base'):
@ -379,12 +379,12 @@ def push(path):
'''
Push a file from the minion up to the master, the file will be saved to
the salt master in the master's minion files cachedir
(defaults to /var/cache/salt/master/minions/files)
(defaults to ``/var/cache/salt/master/minions/minion-id/files``)
Since this feature allows a minion to push a file up to the master server
it is disabled by default for security purposes. To enable add the option:
file_recv: True
to the master configuration and restart the master
it is disabled by default for security purposes. To enable, set
``file_recv`` to ``True`` in the master configuration file, and restart the
master.
CLI Example::

View File

@ -132,7 +132,11 @@ def latest_version(*names, **kwargs):
installed = _cpv_to_version(_vartree().dep_bestmatch(name))
avail = _cpv_to_version(_porttree().dep_bestmatch(name))
if avail:
if not installed or compare(pkg1=installed, oper='<', pkg2=avail):
if not installed \
or salt.utils.compare_versions(ver1=installed,
oper='<',
ver2=avail,
cmp_func=version_cmp):
ret[name] = avail
# Return a string if only one package name passed
@ -221,8 +225,8 @@ def porttree_matches(name):
'''
matches = []
for category in _porttree().dbapi.categories:
if _porttree().dbapi.cp_list(category+"/"+name):
matches.append(category+"/"+name)
if _porttree().dbapi.cp_list(category + "/" + name):
matches.append(category + "/" + name)
return matches
@ -276,7 +280,8 @@ def refresh_db():
if 'makeconf.features_contains'in __salt__ and __salt__['makeconf.features_contains']('webrsync-gpg'):
# GPG sign verify is supported only for "webrsync"
cmd = 'emerge-webrsync -q'
if salt.utils.which('emerge-delta-webrsync'): # We prefer 'delta-webrsync' to 'webrsync'
# We prefer 'delta-webrsync' to 'webrsync'
if salt.utils.which('emerge-delta-webrsync'):
cmd = 'emerge-delta-webrsync -q'
return __salt__['cmd.retcode'](cmd) == 0
else:
@ -284,7 +289,8 @@ def refresh_db():
return True
# We fall back to "webrsync" if "rsync" fails for some reason
cmd = 'emerge-webrsync -q'
if salt.utils.which('emerge-delta-webrsync'): # We prefer 'delta-webrsync' to 'webrsync'
# We prefer 'delta-webrsync' to 'webrsync'
if salt.utils.which('emerge-delta-webrsync'):
cmd = 'emerge-delta-webrsync -q'
return __salt__['cmd.retcode'](cmd) == 0
@ -413,7 +419,7 @@ def install(name=None,
for param, version_num in pkg_params.iteritems():
original_param = param
param = _p_to_cp(param)
if param == None:
if param is None:
raise portage.dep.InvalidAtom(original_param)
if version_num is None:
@ -442,12 +448,12 @@ def install(name=None,
__salt__['portage_config.append_use_flags'](target[1:-1])
new = __salt__['portage_config.get_flags_from_package_conf']('use', target[1:-1])
if old != new:
changes[param+'-USE'] = {'old': old, 'new': new}
changes[param + '-USE'] = {'old': old, 'new': new}
target = target[:target.rfind('[')] + '"'
if keyword != None:
if keyword is not None:
__salt__['portage_config.append_to_package_conf']('accept_keywords', target[1:-1], ['~ARCH'])
changes[param+'-ACCEPT_KEYWORD'] = {'old': '', 'new': '~ARCH'}
changes[param + '-ACCEPT_KEYWORD'] = {'old': '', 'new': '~ARCH'}
targets.append(target)
else:
@ -571,7 +577,7 @@ def remove(name=None, slot=None, fromrepo=None, pkgs=None, **kwargs):
targets = ['{0}:{1}'.format(fullatom, slot)]
if fromrepo is not None:
targets = ['{0}::{1}'.format(fullatom, fromrepo)]
targets = [ fullatom ]
targets = [fullatom]
else:
targets = [x for x in pkg_params if x in old]
@ -656,7 +662,7 @@ def depclean(name=None, slot=None, fromrepo=None, pkgs=None):
targets = ['{0}:{1}'.format(fullatom, slot)]
if fromrepo is not None:
targets = ['{0}::{1}'.format(fullatom, fromrepo)]
targets = [ fullatom ]
targets = [fullatom]
else:
targets = [x for x in pkg_params if x in old]
@ -667,7 +673,7 @@ def depclean(name=None, slot=None, fromrepo=None, pkgs=None):
return __salt__['pkg_resource.find_changes'](old, new)
def perform_cmp(pkg1='', pkg2=''):
def version_cmp(pkg1, pkg2):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
@ -675,8 +681,7 @@ def perform_cmp(pkg1='', pkg2=''):
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
salt '*' pkg.version_cmp '0.2.4-0' '0.2.4.1-0'
'''
regex = r'^~?([^:\[]+):?[^\[]*\[?.*$'
ver1 = re.match(regex, pkg1)
@ -687,18 +692,6 @@ def perform_cmp(pkg1='', pkg2=''):
return None
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)
def version_clean(version):
'''
Clean the version string removing extra data.
@ -752,7 +745,8 @@ def check_extra_requirements(pkgname, pkgver):
des_uses = set(portage.dep.dep_getusedeps(atom))
cur_use = cur_use.split()
if len([ x for x in des_uses.difference(cur_use) if x[0]!='-' or x[1:] in cur_use ]) > 0:
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:

View File

@ -391,32 +391,6 @@ def rehash():
__salt__['cmd.run_all']('rehash')
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)
def file_list(*packages):
'''
List the files that belong to a package. Not specifying any packages will

View File

@ -216,3 +216,30 @@ def ls(): # pylint: disable=C0103
salt '*' grains.ls
'''
return sorted(__grains__)
def filter_by(lookup_dict, grain='os_family'):
'''
Look up the given grain in a given dictionary for the current OS and return
the result
Although this may occasionally be useful at the CLI, the primary intent of
this function is for use in Jinja to make short work of creating lookup
tables for OS-specific data. For example::
{% set pkg_table = {
'Debian': {'name': 'apache2'},
'RedHat': {'name': 'httpd'},
} %}
{% set pkg = salt['grains.filter_by'](pkg_table) %}
myapache:
pkg:
- installed
- name: {{ pkg.name }}
CLI Example::
salt '*' grains.filter_by '{Debian: Debheads rule, RedHat: I love my hat}'
'''
return lookup_dict.get(__grains__[grain], None)

View File

@ -222,29 +222,3 @@ def purge(name=None, pkgs=None, **kwargs):
salt '*' pkg.purge pkgs='["foo", "bar"]'
'''
return remove(name=name, pkgs=pkgs)
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4' '0.2.4.1'
salt '*' pkg.perform_cmp pkg1='0.2.4' pkg2='0.2.4.1'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4' '<' '0.2.4.1'
salt '*' pkg.compare pkg1='0.2.4' oper='<' pkg2='0.2.4.1'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)

View File

@ -396,32 +396,6 @@ def purge(name=None, pkgs=None, **kwargs):
return _uninstall(action='purge', name=name, pkgs=pkgs)
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)
def file_list(*packages):
'''
List the files that belong to a package. Not specifying any packages will

View File

@ -3,7 +3,6 @@ Resources needed by pkg providers
'''
# Import python libs
import distutils.version # pylint: disable=E0611
import fnmatch
import logging
import os
@ -97,7 +96,8 @@ def _parse_pkg_meta(path):
except AttributeError:
continue
if arch:
name += ':{0}'.format(arch)
if cpuarch == 'x86_64' and arch != 'amd64':
name += ':{0}'.format(arch)
return name, version
if __grains__['os_family'] in ('Suse', 'RedHat', 'Mandriva'):
@ -418,62 +418,10 @@ def find_changes(old=None, new=None):
return pkgs
def perform_cmp(pkg1='', pkg2=''):
'''
Compares two version strings using distutils.version.LooseVersion. This is
a fallback for providers which don't have a version comparison utility
built into them. Return -1 if version1 < version2, 0 if version1 ==
version2, and 1 if version1 > version2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg_resource.perform_cmp
'''
try:
if distutils.version.LooseVersion(pkg1) < \
distutils.version.LooseVersion(pkg2):
return -1
elif distutils.version.LooseVersion(pkg1) == \
distutils.version.LooseVersion(pkg2):
return 0
elif distutils.version.LooseVersion(pkg1) > \
distutils.version.LooseVersion(pkg2):
return 1
except Exception as e:
log.exception(e)
return None
def compare(pkg1='', oper='==', pkg2=''):
'''
Package version comparison function.
CLI Example::
salt '*' pkg_resource.compare
'''
cmp_map = {'<': (-1,), '<=': (-1, 0), '==': (0,),
'>=': (0, 1), '>': (1,)}
if oper not in ['!='] + cmp_map.keys():
log.error('Invalid operator "{0}" for package '
'comparison'.format(oper))
return False
cmp_result = __salt__['pkg.perform_cmp'](pkg1, pkg2)
if cmp_result is None:
return False
if oper == '!=':
return cmp_result not in cmp_map['==']
else:
return cmp_result in cmp_map[oper]
def version_clean(version):
'''
Clean the version string removing extra data.
This function will simply try to call "pkg.version_clean".
This function will simply try to call ``pkg.version_clean``.
CLI Example::

View File

@ -423,32 +423,6 @@ def rehash():
__salt__['cmd.run']('rehash')
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)
def file_list(package):
'''
List the files that belong to a package.

View File

@ -857,29 +857,3 @@ def updating(pkg_name, filedate=None, filename=None):
cmd = 'pkg updating {0} {1}'.format(opts, pkg_name)
return __salt__['cmd.run'](cmd)
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)

View File

@ -194,7 +194,9 @@ def latest_version(*names, **kwargs):
if name in names:
cver = pkgs.get(name, '')
nver = version_rev.split(',')[0]
if not cver or compare(pkg1=cver, oper='<', pkg2=nver):
if not cver or salt.utils.compare_versions(ver1=cver,
oper='<',
ver2=nver):
# Remove revision for version comparison
ret[name] = version_rev
@ -326,29 +328,3 @@ def purge(name=None, pkgs=None, **kwargs):
salt '*' pkg.purge pkgs='["foo", "bar"]'
'''
return remove(name=name, pkgs=pkgs)
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)

View File

@ -436,29 +436,3 @@ def purge(name=None, pkgs=None, **kwargs):
salt '*' pkg.purge pkgs='["foo", "bar"]'
'''
return remove(name=name, pkgs=pkgs, **kwargs)
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)

View File

@ -706,29 +706,3 @@ def _get_latest_pkg_version(pkginfo):
return pkginfo.keys().pop()
pkgkeys = pkginfo.keys()
return sorted(pkgkeys, cmp=_reverse_cmp_pkg_versions).pop()
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)

View File

@ -1039,32 +1039,6 @@ def _parse_repo_file(filename):
return (header, repos)
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)
def file_list(*packages):
'''
List the files that belong to a package. Not specifying any packages will

View File

@ -505,29 +505,3 @@ def purge(name=None, pkgs=None, **kwargs):
salt '*' pkg.purge pkgs='["foo", "bar"]'
'''
return remove(name=name, pkgs=pkgs)
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)

View File

@ -419,29 +419,3 @@ def purge(name=None, pkgs=None, **kwargs):
salt '*' pkg.purge pkgs='["foo", "bar"]'
'''
return _uninstall(action='purge', name=name, pkgs=pkgs)
def perform_cmp(pkg1='', pkg2=''):
'''
Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
making the comparison.
CLI Example::
salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)
def compare(pkg1='', oper='==', pkg2=''):
'''
Compare two version strings.
CLI Example::
salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
'''
return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)

View File

@ -58,49 +58,6 @@ def _get_ref(repo, short):
return ref
return False
def _wait_lock(lk_fn, dest):
'''
If the write lock is there, check to see if the file is actually being
written. If there is no change in the file size after a short sleep,
remove the lock and move forward.
'''
if not os.path.isfile(lk_fn):
return False
if not os.path.isfile(dest):
# The dest is not here, sleep for a bit, if the dest is not here yet
# kill the lockfile and start the write
time.sleep(1)
if not os.path.isfile(dest):
try:
os.remove(lk_fn)
except (OSError, IOError):
pass
return False
# There is a lock file, the dest is there, stat the dest, sleep and check
# that the dest is being written, if it is not being written kill the lock
# file and continue. Also check if the lock file is gone.
s_count = 0
s_size = os.stat(dest).st_size
while True:
time.sleep(1)
if not os.path.isfile(lk_fn):
return False
size = os.stat(dest).st_size
if size == s_size:
s_count += 1
if s_count >= 3:
# The file is not being written to, kill the lock and proceed
try:
os.remove(lk_fn)
except (OSError, IOError):
pass
return False
else:
s_size = size
return False
def init(branch, repo_location):
'''
Return the git repo object for this session
@ -125,6 +82,7 @@ def init(branch, repo_location):
repo.create_remote('origin', repo_location)
except Exception:
pass
repo.git.fetch()
return repo
@ -141,14 +99,7 @@ def update(branch, repo_location):
except git.exc.GitCommandError as e:
logging.error('Unable to checkout branch {0}: {1}'.format(branch, e))
return False
lk_fn = os.path.join(repo.working_dir, 'update.lk')
with salt.utils.fopen(lk_fn, 'w+') as fp_:
fp_.write(str(pid))
repo.git.pull()
try:
os.remove(lk_fn)
except (OSError, IOError):
pass
return True

View File

@ -2,11 +2,16 @@
Execute salt convenience routines
'''
# Import python libs
import multiprocessing
import datetime
# Import salt libs
import salt.loader
import salt.exceptions
import salt.utils
import salt.minion
import salt.utils.event
class RunnerClient(object):
@ -17,6 +22,23 @@ class RunnerClient(object):
self.opts = opts
self.functions = salt.loader.runner(opts)
def _proc_runner(self, tag, fun, arg, kwarg):
'''
Run this method in a multiprocess target to execute the runner in a
multiprocess and fire the return data on the event bus
'''
salt.utils.daemonize()
data = {}
try:
data['ret'] = self.cmd(fun, arg, kwarg)
except Exception as exc:
data['ret'] = 'Exception occured in runner {0}: {1}'.format(
fun,
exc,
)
event = salt.utils.event.MasterEvent(self.opts['sock_dir'])
event.fire_event(data, tag)
def _verify_fun(self, fun):
'''
Check that the function passed really exists
@ -57,6 +79,19 @@ class RunnerClient(object):
ret = l_fun(*f_call.get('args', ()), **f_call.get('kwargs', {}))
return ret
def async(self, fun, arg, kwarg=None):
'''
Execute the runner in a multiprocess and return the event tag to use
to watch for the return
'''
tag = '{0:%Y%m%d%H%M%S%f}'.format(datetime.datetime.now())
tag[20] = 'r'
proc = multiprocessing.Process(
target=self._proc_runner,
args=(tag, fun, arg, kwarg))
proc.start()
return tag
class Runner(RunnerClient):
'''

View File

@ -21,7 +21,7 @@ import salt.minion
def decode_list(data):
'''
JSON decodes as unicode, Jinja needs raw strings...
JSON decodes as unicode, Jinja needs bytes...
'''
rv = []
for item in data:
@ -37,25 +37,25 @@ def decode_list(data):
def decode_dict(data):
'''
JSON decodes as unicode, Jinja needs raw strings...
JSON decodes as unicode, Jinja needs bytes...
'''
rv = {}
for key, value in data.iteritems():
if isinstance(key, unicode):
key = key.encode('utf-8')
key = key.encode('utf-8')
if isinstance(value, unicode):
value = value.encode('utf-8')
value = value.encode('utf-8')
elif isinstance(value, list):
value = decode_list(value)
value = decode_list(value)
elif isinstance(value, dict):
value = decode_dict(value)
value = decode_dict(value)
rv[key] = value
return rv
class SSH(object):
'''
Create an ssh execution system
Create an SSH execution system
'''
def __init__(self, opts):
self.opts = opts
@ -85,7 +85,7 @@ class SSH(object):
def get_pubkey(self):
'''
Return the keystring for the ssh public key
Return the keystring for the SSH public key
'''
priv = self.opts.get(
'ssh_priv',
@ -101,7 +101,7 @@ class SSH(object):
def key_deploy(self, host, ret):
'''
Deploy the ssh key if the minions don't auth
Deploy the SSH key if the minions don't auth
'''
if not isinstance(ret[host], basestring):
return ret
@ -405,7 +405,7 @@ class Single():
class FunctionWrapper(dict):
'''
Create an object that acts like the salt function dict and makes function
calls remotely via the ssh shell system
calls remotely via the SSH shell system
'''
def __init__(
self,
@ -440,7 +440,7 @@ class FunctionWrapper(dict):
class SSHState(salt.state.State):
'''
Create a State object which wraps the ssh functions for state operations
Create a State object which wraps the SSH functions for state operations
'''
def __init__(self, opts, pillar=None, wrapper=None):
self.wrapper = wrapper

View File

@ -1241,7 +1241,7 @@ def recurse(name,
if not _src_path:
pass
elif _src_path.strip('/') not in __salt__['cp.list_master_dirs'](env):
elif _src_path.strip('/') not in __salt__['cp.list_master_dirs'](env, _src_path):
ret['result'] = False
ret['comment'] = (
'The source: {0} does not exist on the master'.format(source)
@ -1353,11 +1353,9 @@ def recurse(name,
#we're searching for things that start with this *directory*.
# use '/' since #master only runs on POSIX
srcpath = srcpath + '/'
for fn_ in __salt__['cp.list_master'](env):
for fn_ in __salt__['cp.list_master'](env, srcpath):
if not fn_.strip():
continue
if not fn_.startswith(srcpath):
continue
# fn_ here is the absolute (from file_roots) source path of
# the file to copy from; it is either a normal file or an
@ -1393,10 +1391,8 @@ def recurse(name,
manage_file(dest, src)
if include_empty:
mdirs = __salt__['cp.list_master_dirs'](env)
mdirs = __salt__['cp.list_master_dirs'](env, srcpath)
for mdir in mdirs:
if not mdir.startswith(srcpath):
continue
if not _check_include_exclude(os.path.relpath(mdir, srcpath),
include_pat,
exclude_pat):

View File

@ -19,6 +19,7 @@ requisite to a pkg.installed state for the package which provides pip
'''
# Import python libs
import re
import urlparse
# Import salt libs
@ -33,6 +34,34 @@ def __virtual__():
return 'pip' if 'pip.list' in __salt__ else False
def _find_key(prefix, pip_list):
'''
Does a case-insensitive match in the pip_list for the desired package.
'''
try:
match = next(
iter(x for x in pip_list if x.lower() == prefix.lower())
)
except StopIteration:
return None
else:
return match
def _fulfills_version_spec(version, version_spec):
'''
Check version number against version specification info and return a
boolean value based on whether or not the version number meets the
specified version.
'''
for oper, spec in (version_spec[0:2], version_spec[2:4]):
if oper is None:
continue
if not salt.utils.compare_versions(ver1=version, oper=oper, ver2=spec):
return False
return True
def installed(name,
pip_bin=None,
requirements=None,
@ -87,17 +116,38 @@ def installed(name,
elif env and not bin_env:
bin_env = env
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
scheme, netloc, path, query, fragment = urlparse.urlsplit(name)
if scheme and netloc:
# parse as VCS url
prefix = path.lstrip('/').split('@', 1)[0]
if scheme.startswith("git+"):
prefix = prefix.rstrip(".git")
if scheme.startswith('git+'):
prefix = prefix.rstrip('.git')
else:
# Pull off any requirements specifiers
prefix = name.split('=')[0].split('<')[0].split('>')[0].strip()
# Split the passed string into the prefix and version
try:
version_spec = list(re.match(
(r'([^=<>]+)(?:(?:([<>]=?|==?)([^<>=,]+))'
r'(?:,([<>]=?|==?)([^<>=]+))?)?$'),
name
).groups())
prefix = version_spec.pop(0)
except AttributeError:
ret['result'] = False
ret['comment'] = 'Invalidly-formatted package {0}'.format(name)
return ret
else:
# Check to see if '=' was used instead of '=='. version_spec will
# contain two sets of comparison operators and version numbers, so
# we are checking elements 0 and 2 of this list.
if any((version_spec[x] == '=' for x in (0, 2))):
ret['result'] = False
ret['comment'] = ('Invalid version specification in '
'package {0}. \'=\' is not supported, use '
'\'==\' instead.'.format(name))
return ret
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
if runas is not None:
# The user is using a deprecated argument, warn!
msg = (
@ -107,41 +157,48 @@ def installed(name,
salt.utils.warn_until((0, 18), msg)
ret.setdefault('warnings', []).append(msg)
# "There can only be one"
if runas is not None and user:
raise CommandExecutionError(
'The \'runas\' and \'user\' arguments are mutually exclusive. '
'Please use \'user\' as \'runas\' is being deprecated.'
)
# Support deprecated 'runas' arg
elif runas is not None and not user:
user = runas
# "There can only be one"
if user:
raise CommandExecutionError(
'The \'runas\' and \'user\' arguments are mutually exclusive. '
'Please use \'user\' as \'runas\' is being deprecated.'
)
# Support deprecated 'runas' arg
else:
user = runas
try:
pip_list = __salt__['pip.list'](prefix, bin_env, user=user, cwd=cwd)
pip_list = __salt__['pip.list'](prefix, bin_env=bin_env,
user=user, cwd=cwd)
prefix_realname = _find_key(prefix, pip_list)
except (CommandNotFoundError, CommandExecutionError) as err:
ret['result'] = False
ret['comment'] = 'Error installing \'{0}\': {1}'.format(name, err)
return ret
if ignore_installed is False and prefix.lower() in (p.lower()
for p in pip_list):
if force_reinstall is False and upgrade is False:
ret['result'] = True
ret['comment'] = 'Package already installed'
return ret
if ignore_installed is False and prefix_realname is not None:
if force_reinstall is False and not upgrade:
# Check desired version (if any) against currently-installed
if (
any(version_spec) and
_fulfills_version_spec(pip_list[prefix_realname],
version_spec)
) or (not any(version_spec)):
ret['result'] = True
ret['comment'] = ('Python package {0} already '
'installed'.format(name))
return ret
if __opts__['test']:
ret['result'] = None
ret['comment'] = 'Python package {0} is set to be installed'.format(
name)
ret['comment'] = \
'Python package {0} is set to be installed'.format(name)
return ret
# Replace commas (used for version ranges) with semicolons (which are not
# supported) in name so it does not treat them as multiple packages. Comma
# will be re-added in pip.install call. Wrap in double quotes to allow for
# version ranges
name = '"' + name.replace(',', ';') + '"'
# will be re-added in pip.install call.
name = name.replace(',', ';')
if repo:
name = repo
@ -153,7 +210,7 @@ def installed(name,
name = ''
pip_install_call = __salt__['pip.install'](
pkgs=name,
pkgs='"{0}"'.format(name),
requirements=requirements,
bin_env=bin_env,
log=log,

View File

@ -48,8 +48,10 @@ if salt.utils.is_windows():
from salt.modules.win_pkg import _reverse_cmp_pkg_versions
_get_package_info = namespaced_function(_get_package_info, globals())
get_repo_data = namespaced_function(get_repo_data, globals())
_get_latest_pkg_version = namespaced_function(_get_latest_pkg_version, globals())
_reverse_cmp_pkg_versions = namespaced_function(_reverse_cmp_pkg_versions, globals())
_get_latest_pkg_version = \
namespaced_function(_get_latest_pkg_version, globals())
_reverse_cmp_pkg_versions = \
namespaced_function(_reverse_cmp_pkg_versions, globals())
# The following imports are used by the namespaced win_pkg funcs
# and need to be included in their globals.
import msgpack
@ -65,13 +67,16 @@ def __gen_rtag():
return os.path.join(__opts__['cachedir'], 'pkg_refresh')
def _fulfills_version_spec(versions, oper, desired_version):
def _fulfills_version_spec(version, oper, desired_version):
'''
Returns True if any of the installed versions match the specified version,
otherwise returns False
'''
for ver in versions:
if __salt__['pkg.compare'](pkg1=ver, oper=oper, pkg2=desired_version):
for ver in version:
if salt.utils.compare_versions(ver1=version,
oper=oper,
ver2=desired_version,
cmp_func=__salt__.get('version_cmp')):
return True
return False
@ -153,7 +158,8 @@ def _find_install_targets(name=None, version=None, pkgs=None, sources=None):
if not cver:
targets[pkgname] = pkgver
continue
elif not __salt__['pkg_resource.check_extra_requirements'](pkgname, pkgver):
elif not __salt__['pkg_resource.check_extra_requirements'](pkgname,
pkgver):
targets[pkgname] = pkgver
continue
# No version specified and pkg is installed, do not add to targets
@ -170,7 +176,7 @@ def _find_install_targets(name=None, version=None, pkgs=None, sources=None):
comparison = gt_lt or ''
comparison += eq or ''
# A comparison operator of "=" is redundant, but possible.
# Change it to "==" so that it works in pkg.compare.
# Change it to "==" so that the version comparison works
if comparison in ['=', '']:
comparison = '=='
if not _fulfills_version_spec(cver, comparison, verstr):
@ -215,7 +221,7 @@ def _verify_install(desired, new_pkgs):
comparison = gt_lt or ''
comparison += eq or ''
# A comparison operator of "=" is redundant, but possible.
# Change it to "==" so that it works in pkg.compare.
# Change it to "==" so that the version comparison works.
if comparison in ('=', ''):
comparison = '=='
if _fulfills_version_spec(cver, comparison, verstr):
@ -557,10 +563,12 @@ def latest(
msg = 'No information found for "{0}".'.format(pkg)
log.error(msg)
problems.append(msg)
elif not cur[pkg] or \
__salt__['pkg.compare'](pkg1=cur[pkg],
oper='<',
pkg2=avail[pkg]):
elif not cur[pkg] \
or salt.utils.compare_versions(
ver1=cur[pkg],
oper='<',
ver2=avail[pkg],
cmp_func=__salt__.get('version_cmp')):
targets[pkg] = avail[pkg]
if problems:
@ -608,7 +616,8 @@ def latest(
if changes:
# Find failed and successful updates
failed = [x for x in targets if changes[x]['new'] != targets[x]]
failed = [x for x in targets
if not changes.get(x) or changes[x]['new'] != targets[x]]
successful = [x for x in targets if x not in failed]
comments = []

View File

@ -5,6 +5,7 @@ from __future__ import absolute_import
# Import python libs
import datetime
import distutils.version # pylint: disable=E0611
import fnmatch
import hashlib
import imp
@ -1389,3 +1390,51 @@ def warn_until(version_info,
if _dont_call_warnings is False:
warnings.warn(message, category, stacklevel=stacklevel)
def version_cmp(pkg1, pkg2):
'''
Compares two version strings using distutils.version.LooseVersion. This is
a fallback for providers which don't have a version comparison utility
built into them. Return -1 if version1 < version2, 0 if version1 ==
version2, and 1 if version1 > version2. Return None if there was a problem
making the comparison.
'''
try:
if distutils.version.LooseVersion(pkg1) < \
distutils.version.LooseVersion(pkg2):
return -1
elif distutils.version.LooseVersion(pkg1) == \
distutils.version.LooseVersion(pkg2):
return 0
elif distutils.version.LooseVersion(pkg1) > \
distutils.version.LooseVersion(pkg2):
return 1
except Exception as e:
log.exception(e)
return None
def compare_versions(ver1='', oper='==', ver2='', cmp_func=None):
'''
Compares two version numbers. Accepts a custom function to perform the
cmp-style version comparison, otherwise uses version_cmp().
'''
cmp_map = {'<': (-1,), '<=': (-1, 0), '==': (0,),
'>=': (0, 1), '>': (1,)}
if oper not in ['!='] + cmp_map.keys():
log.error('Invalid operator "{0}" for version '
'comparison'.format(oper))
return False
if cmp_func is None:
cmp_func = version_cmp
cmp_result = cmp_func(ver1, ver2)
if cmp_result is None:
return False
if oper == '!=':
return cmp_result not in cmp_map['==']
else:
return cmp_result in cmp_map[oper]