Merge branch 'develop' into add-vmware-vm-management

This commit is contained in:
Nicole Thomas 2017-12-04 16:41:37 -05:00 committed by GitHub
commit 2ad2d4e27d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 3806 additions and 305 deletions

4
.github/stale.yml vendored
View File

@ -1,8 +1,8 @@
# Probot Stale configuration file
# Number of days of inactivity before an issue becomes stale
# 875 is approximately 2 years and 5 months
daysUntilStale: 875
# 860 is approximately 2 years and 4 months
daysUntilStale: 860
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7

View File

@ -78,7 +78,7 @@ UNIX systems
**BSD**:
- OpenBSD (``pip`` installation)
- OpenBSD
- FreeBSD 9/10/11
**SunOS**:
@ -272,66 +272,118 @@ Here's a summary of the command line options:
$ sh bootstrap-salt.sh -h
Usage : bootstrap-salt.sh [options] <install-type> <install-type-args>
Installation types:
- stable (default)
- stable [version] (ubuntu specific)
- daily (ubuntu specific)
- testing (redhat specific)
- git
- stable Install latest stable release. This is the default
install type
- stable [branch] Install latest version on a branch. Only supported
for packages available at repo.saltstack.com
- stable [version] Install a specific version. Only supported for
packages available at repo.saltstack.com
- daily Ubuntu specific: configure SaltStack Daily PPA
- testing RHEL-family specific: configure EPEL testing repo
- git Install from the head of the develop branch
- git [ref] Install from any git ref (such as a branch, tag, or
commit)
Examples:
- bootstrap-salt.sh
- bootstrap-salt.sh stable
- bootstrap-salt.sh stable 2014.7
- bootstrap-salt.sh stable 2017.7
- bootstrap-salt.sh stable 2017.7.2
- bootstrap-salt.sh daily
- bootstrap-salt.sh testing
- bootstrap-salt.sh git
- bootstrap-salt.sh git develop
- bootstrap-salt.sh git v0.17.0
- bootstrap-salt.sh git 8c3fadf15ec183e5ce8c63739850d543617e4357
- bootstrap-salt.sh git 2017.7
- bootstrap-salt.sh git v2017.7.2
- bootstrap-salt.sh git 06f249901a2e2f1ed310d58ea3921a129f214358
Options:
-h Display this message
-v Display script version
-n No colours.
-D Show debug output.
-c Temporary configuration directory
-g Salt repository URL. (default: git://github.com/saltstack/salt.git)
-G Instead of cloning from git://github.com/saltstack/salt.git, clone from https://github.com/saltstack/salt.git (Usually necessary on systems which have the regular git protocol port blocked, where https usually is not)
-k Temporary directory holding the minion keys which will pre-seed
the master.
-s Sleep time used when waiting for daemons to start, restart and when checking
for the services running. Default: 3
-M Also install salt-master
-S Also install salt-syndic
-N Do not install salt-minion
-X Do not start daemons after installation
-C Only run the configuration function. This option automatically
bypasses any installation.
-P Allow pip based installations. On some distributions the required salt
packages or its dependencies are not available as a package for that
distribution. Using this flag allows the script to use pip as a last
resort method. NOTE: This only works for functions which actually
implement pip based installations.
-F Allow copied files to overwrite existing(config, init.d, etc)
-U If set, fully upgrade the system prior to bootstrapping salt
-K If set, keep the temporary files in the temporary directories specified
with -c and -k.
-I If set, allow insecure connections while downloading any files. For
example, pass '--no-check-certificate' to 'wget' or '--insecure' to 'curl'
-A Pass the salt-master DNS name or IP. This will be stored under
${BS_SALT_ETC_DIR}/minion.d/99-master-address.conf
-i Pass the salt-minion id. This will be stored under
${BS_SALT_ETC_DIR}/minion_id
-L Install the Apache Libcloud package if possible(required for salt-cloud)
-p Extra-package to install while installing salt dependencies. One package
per -p flag. You're responsible for providing the proper package name.
-d Disable check_service functions. Setting this flag disables the
'install_<distro>_check_services' checks. You can also do this by
touching /tmp/disable_salt_checks on the target host. Defaults ${BS_FALSE}
-H Use the specified http proxy for the installation
-Z Enable external software source for newer ZeroMQ(Only available for RHEL/CentOS/Fedora/Ubuntu based distributions)
-b Assume that dependencies are already installed and software sources are set up.
If git is selected, git tree is still checked out as dependency step.
-h Display this message
-v Display script version
-n No colours
-D Show debug output
-c Temporary configuration directory
-g Salt Git repository URL. Default: https://github.com/saltstack/salt.git
-w Install packages from downstream package repository rather than
upstream, saltstack package repository. This is currently only
implemented for SUSE.
-k Temporary directory holding the minion keys which will pre-seed
the master.
-s Sleep time used when waiting for daemons to start, restart and when
checking for the services running. Default: 3
-L Also install salt-cloud and required python-libcloud package
-M Also install salt-master
-S Also install salt-syndic
-N Do not install salt-minion
-X Do not start daemons after installation
-d Disables checking if Salt services are enabled to start on system boot.
You can also do this by touching /tmp/disable_salt_checks on the target
host. Default: ${BS_FALSE}
-P Allow pip based installations. On some distributions the required salt
packages or its dependencies are not available as a package for that
distribution. Using this flag allows the script to use pip as a last
resort method. NOTE: This only works for functions which actually
implement pip based installations.
-U If set, fully upgrade the system prior to bootstrapping Salt
-I If set, allow insecure connections while downloading any files. For
example, pass '--no-check-certificate' to 'wget' or '--insecure' to
'curl'. On Debian and Ubuntu, using this option with -U allows to obtain
GnuPG archive keys insecurely if distro has changed release signatures.
-F Allow copied files to overwrite existing (config, init.d, etc)
-K If set, keep the temporary files in the temporary directories specified
with -c and -k
-C Only run the configuration function. Implies -F (forced overwrite).
To overwrite Master or Syndic configs, -M or -S, respectively, must
also be specified. Salt installation will be ommitted, but some of the
dependencies could be installed to write configuration with -j or -J.
-A Pass the salt-master DNS name or IP. This will be stored under
${BS_SALT_ETC_DIR}/minion.d/99-master-address.conf
-i Pass the salt-minion id. This will be stored under
${BS_SALT_ETC_DIR}/minion_id
-p Extra-package to install while installing Salt dependencies. One package
per -p flag. You're responsible for providing the proper package name.
-H Use the specified HTTP proxy for all download URLs (including https://).
For example: http://myproxy.example.com:3128
-Z Enable additional package repository for newer ZeroMQ
(only available for RHEL/CentOS/Fedora/Ubuntu based distributions)
-b Assume that dependencies are already installed and software sources are
set up. If git is selected, git tree is still checked out as dependency
step.
-f Force shallow cloning for git installations.
This may result in an "n/a" in the version number.
-l Disable ssl checks. When passed, switches "https" calls to "http" where
possible.
-V Install Salt into virtualenv
(only available for Ubuntu based distributions)
-a Pip install all Python pkg dependencies for Salt. Requires -V to install
all pip pkgs into the virtualenv.
(Only available for Ubuntu based distributions)
-r Disable all repository configuration performed by this script. This
option assumes all necessary repository configuration is already present
on the system.
-R Specify a custom repository URL. Assumes the custom repository URL
points to a repository that mirrors Salt packages located at
repo.saltstack.com. The option passed with -R replaces the
"repo.saltstack.com". If -R is passed, -r is also set. Currently only
works on CentOS/RHEL and Debian based distributions.
-J Replace the Master config file with data passed in as a JSON string. If
a Master config file is found, a reasonable effort will be made to save
the file with a ".bak" extension. If used in conjunction with -C or -F,
no ".bak" file will be created as either of those options will force
a complete overwrite of the file.
-j Replace the Minion config file with data passed in as a JSON string. If
a Minion config file is found, a reasonable effort will be made to save
the file with a ".bak" extension. If used in conjunction with -C or -F,
no ".bak" file will be created as either of those options will force
a complete overwrite of the file.
-q Quiet salt installation from git (setup.py install -q)
-x Changes the python version used to install a git version of salt. Currently
this is considered experimental and has only been tested on Centos 6. This
only works for git installations.
-y Installs a different python version on host. Currently this has only been
tested with Centos 6 and is considered experimental. This will install the
ius repo on the box if disable repo is false. This must be used in conjunction
with -x <pythonversion>. For example:
sh bootstrap.sh -P -y -x python2.7 git v2016.11.3
The above will install python27 and install the git version of salt using the
python2.7 executable. This only works for git and pip installations.

View File

@ -474,8 +474,14 @@ def _sunos_memdata():
grains['mem_total'] = int(comps[2].strip())
swap_cmd = salt.utils.path.which('swap')
swap_total = __salt__['cmd.run']('{0} -s'.format(swap_cmd)).split()[1]
grains['swap_total'] = int(swap_total) // 1024
swap_data = __salt__['cmd.run']('{0} -s'.format(swap_cmd)).split()
try:
swap_avail = int(swap_data[-2][:-1])
swap_used = int(swap_data[-4][:-1])
swap_total = (swap_avail + swap_used) // 1024
except ValueError:
swap_total = None
grains['swap_total'] = swap_total
return grains

View File

@ -1593,8 +1593,10 @@ class LazyLoader(salt.utils.lazy.LazyDict):
Load a single item if you have it
'''
# if the key doesn't have a '.' then it isn't valid for this mod dict
if not isinstance(key, six.string_types) or u'.' not in key:
raise KeyError
if not isinstance(key, six.string_types):
raise KeyError(u'The key must be a string.')
if u'.' not in key:
raise KeyError(u'The key \'%s\' should contain a \'.\'', key)
mod_name, _ = key.split(u'.', 1)
if mod_name in self.missing_modules:
return True

View File

@ -2067,12 +2067,16 @@ class Minion(MinionBase):
self.schedule.run_job(name)
elif func == u'disable_job':
self.schedule.disable_job(name, persist)
elif func == u'postpone_job':
self.schedule.postpone_job(name, data)
elif func == u'reload':
self.schedule.reload(schedule)
elif func == u'list':
self.schedule.list(where)
elif func == u'save_schedule':
self.schedule.save_schedule()
elif func == u'get_next_fire_time':
self.schedule.get_next_fire_time(name)
def manage_beacons(self, tag, data):
'''

View File

@ -214,14 +214,12 @@ def __virtual__():
'''
ret = ansible is not None
msg = not ret and "Ansible is not installed on this system" or None
if msg:
log.warning(msg)
else:
if ret:
global _resolver
global _caller
_resolver = AnsibleModuleResolver(__opts__).resolve().install()
_caller = AnsibleModuleCaller(_resolver)
_set_callables(list())
_set_callables(list())
return ret, msg

View File

@ -589,11 +589,17 @@ def _run(cmd,
out = proc.stdout.decode(__salt_system_encoding__)
except AttributeError:
out = u''
except UnicodeDecodeError:
log.error('UnicodeDecodeError while decoding output of cmd {0}'.format(cmd))
out = proc.stdout.decode(__salt_system_encoding__, 'replace')
try:
err = proc.stderr.decode(__salt_system_encoding__)
except AttributeError:
err = u''
except UnicodeDecodeError:
log.error('UnicodeDecodeError while decoding error of cmd {0}'.format(cmd))
err = proc.stderr.decode(__salt_system_encoding__, 'replace')
if rstrip:
if out is not None:

View File

@ -552,11 +552,11 @@ def lsattr(path):
raise SaltInvocationError("File or directory does not exist.")
cmd = ['lsattr', path]
result = __salt__['cmd.run'](cmd, python_shell=False)
result = __salt__['cmd.run'](cmd, ignore_retcode=True, python_shell=False)
results = {}
for line in result.splitlines():
if not line.startswith('lsattr'):
if not line.startswith('lsattr: '):
vals = line.split(None, 1)
results[vals[1]] = re.findall(r"[acdijstuADST]", vals[0])
@ -5203,13 +5203,18 @@ def manage_file(name,
'Replace symbolic link with regular file'
if salt.utils.platform.is_windows():
ret = check_perms(name,
ret,
kwargs.get('win_owner'),
kwargs.get('win_perms'),
kwargs.get('win_deny_perms'),
None,
kwargs.get('win_inheritance'))
# This function resides in win_file.py and will be available
# on Windows. The local function will be overridden
# pylint: disable=E1120,E1121,E1123
ret = check_perms(
path=name,
ret=ret,
owner=kwargs.get('win_owner'),
grant_perms=kwargs.get('win_perms'),
deny_perms=kwargs.get('win_deny_perms'),
inheritance=kwargs.get('win_inheritance', True),
reset=kwargs.get('win_perms_reset', False))
# pylint: enable=E1120,E1121,E1123
else:
ret, _ = check_perms(name, ret, user, group, mode, attrs, follow_symlinks)
@ -5250,13 +5255,15 @@ def manage_file(name,
if salt.utils.platform.is_windows():
# This function resides in win_file.py and will be available
# on Windows. The local function will be overridden
# pylint: disable=E1121
makedirs_(name,
kwargs.get('win_owner'),
kwargs.get('win_perms'),
kwargs.get('win_deny_perms'),
kwargs.get('win_inheritance'))
# pylint: enable=E1121
# pylint: disable=E1120,E1121,E1123
makedirs_(
path=name,
owner=kwargs.get('win_owner'),
grant_perms=kwargs.get('win_perms'),
deny_perms=kwargs.get('win_deny_perms'),
inheritance=kwargs.get('win_inheritance', True),
reset=kwargs.get('win_perms_reset', False))
# pylint: enable=E1120,E1121,E1123
else:
makedirs_(name, user=user, group=group, mode=dir_mode)
@ -5369,13 +5376,18 @@ def manage_file(name,
mode = oct((0o777 ^ mask) & 0o666)
if salt.utils.platform.is_windows():
ret = check_perms(name,
ret,
kwargs.get('win_owner'),
kwargs.get('win_perms'),
kwargs.get('win_deny_perms'),
None,
kwargs.get('win_inheritance'))
# This function resides in win_file.py and will be available
# on Windows. The local function will be overridden
# pylint: disable=E1120,E1121,E1123
ret = check_perms(
path=name,
ret=ret,
owner=kwargs.get('win_owner'),
grant_perms=kwargs.get('win_perms'),
deny_perms=kwargs.get('win_deny_perms'),
inheritance=kwargs.get('win_inheritance', True),
reset=kwargs.get('win_perms_reset', False))
# pylint: enable=E1120,E1121,E1123
else:
ret, _ = check_perms(name, ret, user, group, mode, attrs)

873
salt/modules/keystoneng.py Normal file
View File

@ -0,0 +1,873 @@
# -*- coding: utf-8 -*-
'''
Keystone module for interacting with OpenStack Keystone
.. versionadded:: Nitrogen
:depends:shade
Example configuration
.. code-block:: yaml
keystone:
cloud: default
.. code-block:: yaml
keystone:
auth:
username: admin
password: password123
user_domain_name: mydomain
project_name: myproject
project_domain_name: myproject
auth_url: https://example.org:5000/v3
identity_api_version: 3
'''
from __future__ import absolute_import
import salt.utils
HAS_SHADE = False
try:
import shade
from shade.exc import OpenStackCloudException
HAS_SHADE = True
except ImportError:
pass
__virtualname__ = 'keystoneng'
def __virtual__():
'''
Only load this module if shade python module is installed
'''
if HAS_SHADE:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def compare_changes(obj, **kwargs):
'''
Compare two dicts returning only keys that exist in the first dict and are
different in the second one
'''
changes = {}
for k, v in obj.items():
if k in kwargs:
if v != kwargs[k]:
changes[k] = kwargs[k]
return changes
def get_entity(ent_type, **kwargs):
'''
Attempt to query Keystone for more information about an entity
'''
try:
func = 'keystoneng.{}_get'.format(ent_type)
ent = __salt__[func](**kwargs)
except OpenStackCloudException as e:
# NOTE(SamYaple): If this error was something other than Forbidden we
# reraise the issue since we are not prepared to handle it
if 'HTTP 403' not in e.inner_exception[1][0]:
raise
# NOTE(SamYaple): The user may be authorized to perform the function
# they are trying to do, but not authorized to search. In such a
# situation we want to trust that the user has passed a valid id, even
# though we cannot validate that this is a valid id
ent = kwargs['name']
return ent
def _clean_kwargs(keep_name=False, **kwargs):
'''
Sanatize the the arguments for use with shade
'''
if 'name' in kwargs and not keep_name:
kwargs['name_or_id'] = kwargs.pop('name')
try:
clean_func = salt.utils.args.clean_kwargs
except AttributeError:
clean_func = salt.utils.clean_kwargs
return clean_func(**kwargs)
def setup_clouds(auth=None):
'''
Call functions to create Shade cloud objects in __context__ to take
advantage of Shade's in-memory caching across several states
'''
get_operator_cloud(auth)
get_openstack_cloud(auth)
def get_operator_cloud(auth=None):
'''
Return an operator_cloud
'''
if auth is None:
auth = __salt__['config.option']('keystone', {})
if 'shade_opcloud' in __context__:
if __context__['shade_opcloud'].auth == auth:
return __context__['shade_opcloud']
__context__['shade_opcloud'] = shade.operator_cloud(**auth)
return __context__['shade_opcloud']
def get_openstack_cloud(auth=None):
'''
Return an openstack_cloud
'''
if auth is None:
auth = __salt__['config.option']('keystone', {})
if 'shade_oscloud' in __context__:
if __context__['shade_oscloud'].auth == auth:
return __context__['shade_oscloud']
__context__['shade_oscloud'] = shade.openstack_cloud(**auth)
return __context__['shade_oscloud']
def group_create(auth=None, **kwargs):
'''
Create a group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_create name=group1
salt '*' keystoneng.group_create name=group2 domain=domain1 description='my group2'
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_group(**kwargs)
def group_delete(auth=None, **kwargs):
'''
Delete a group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_delete name=group1
salt '*' keystoneng.group_delete name=group2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.group_delete name=0e4febc2a5ab4f2c8f374b054162506d
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_group(**kwargs)
def group_update(auth=None, **kwargs):
'''
Update a group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_update name=group1 description='new description'
salt '*' keystoneng.group_create name=group2 domain_id=b62e76fbeeff4e8fb77073f591cf211e new_name=newgroupname
salt '*' keystoneng.group_create name=0e4febc2a5ab4f2c8f374b054162506d new_name=newgroupname
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
if 'new_name' in kwargs:
kwargs['name'] = kwargs.pop('new_name')
return cloud.update_group(**kwargs)
def group_list(auth=None, **kwargs):
'''
List groups
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_list
salt '*' keystoneng.group_list domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_groups(**kwargs)
def group_search(auth=None, **kwargs):
'''
Search for groups
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_search name=group1
salt '*' keystoneng.group_search domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_groups(**kwargs)
def group_get(auth=None, **kwargs):
'''
Get a single group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.group_get name=group1
salt '*' keystoneng.group_get name=group2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.group_get name=0e4febc2a5ab4f2c8f374b054162506d
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_group(**kwargs)
def project_create(auth=None, **kwargs):
'''
Create a project
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_create name=project1
salt '*' keystoneng.project_create name=project2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.project_create name=project3 enabled=False description='my project3'
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_project(**kwargs)
def project_delete(auth=None, **kwargs):
'''
Delete a project
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_delete name=project1
salt '*' keystoneng.project_delete name=project2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.project_delete name=f315afcf12f24ad88c92b936c38f2d5a
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_project(**kwargs)
def project_update(auth=None, **kwargs):
'''
Update a project
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_update name=project1 new_name=newproject
salt '*' keystoneng.project_update name=project2 enabled=False description='new description'
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
if 'new_name' in kwargs:
kwargs['name'] = kwargs.pop('new_name')
return cloud.update_project(**kwargs)
def project_list(auth=None, **kwargs):
'''
List projects
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_list
salt '*' keystoneng.project_list domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_projects(**kwargs)
def project_search(auth=None, **kwargs):
'''
Search projects
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_search
salt '*' keystoneng.project_search name=project1
salt '*' keystoneng.project_search domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_projects(**kwargs)
def project_get(auth=None, **kwargs):
'''
Get a single project
CLI Example:
.. code-block:: bash
salt '*' keystoneng.project_get name=project1
salt '*' keystoneng.project_get name=project2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.project_get name=f315afcf12f24ad88c92b936c38f2d5a
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_project(**kwargs)
def domain_create(auth=None, **kwargs):
'''
Create a domain
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_create name=domain1
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_domain(**kwargs)
def domain_delete(auth=None, **kwargs):
'''
Delete a domain
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_delete name=domain1
salt '*' keystoneng.domain_delete name=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_domain(**kwargs)
def domain_update(auth=None, **kwargs):
'''
Update a domain
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_update name=domain1 new_name=newdomain
salt '*' keystoneng.domain_update name=domain1 enabled=True description='new description'
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
if 'new_name' in kwargs:
kwargs['name'] = kwargs.pop('new_name')
return cloud.update_domain(**kwargs)
def domain_list(auth=None, **kwargs):
'''
List domains
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_list
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_domains(**kwargs)
def domain_search(auth=None, **kwargs):
'''
Search domains
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_search
salt '*' keystoneng.domain_search name=domain1
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_domains(**kwargs)
def domain_get(auth=None, **kwargs):
'''
Get a single domain
CLI Example:
.. code-block:: bash
salt '*' keystoneng.domain_get name=domain1
salt '*' keystoneng.domain_get name=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_domain(**kwargs)
def role_create(auth=None, **kwargs):
'''
Create a role
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_create name=role1
salt '*' keystoneng.role_create name=role1 domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_role(**kwargs)
def role_delete(auth=None, **kwargs):
'''
Delete a role
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_delete name=role1 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.role_delete name=1eb6edd5525e4ac39af571adee673559
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_role(**kwargs)
def role_update(auth=None, **kwargs):
'''
Update a role
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_update name=role1 new_name=newrole
salt '*' keystoneng.role_update name=1eb6edd5525e4ac39af571adee673559 new_name=newrole
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
if 'new_name' in kwargs:
kwargs['name'] = kwargs.pop('new_name')
return cloud.update_role(**kwargs)
def role_list(auth=None, **kwargs):
'''
List roles
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_list
salt '*' keystoneng.role_list domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_roles(**kwargs)
def role_search(auth=None, **kwargs):
'''
Search roles
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_search
salt '*' keystoneng.role_search name=role1
salt '*' keystoneng.role_search domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_roles(**kwargs)
def role_get(auth=None, **kwargs):
'''
Get a single role
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_get name=role1
salt '*' keystoneng.role_get name=role1 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.role_get name=1eb6edd5525e4ac39af571adee673559
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_role(**kwargs)
def user_create(auth=None, **kwargs):
'''
Create a user
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_create name=user1
salt '*' keystoneng.user_create name=user2 password=1234 enabled=False
salt '*' keystoneng.user_create name=user3 domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_user(**kwargs)
def user_delete(auth=None, **kwargs):
'''
Delete a user
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_delete name=user1
salt '*' keystoneng.user_delete name=user2 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.user_delete name=a42cbbfa1e894e839fd0f584d22e321f
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_user(**kwargs)
def user_update(auth=None, **kwargs):
'''
Update a user
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_update name=user1 enabled=False description='new description'
salt '*' keystoneng.user_update name=user1 new_name=newuser
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
if 'new_name' in kwargs:
kwargs['name'] = kwargs.pop('new_name')
return cloud.update_user(**kwargs)
def user_list(auth=None, **kwargs):
'''
List users
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_list
salt '*' keystoneng.user_list domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_users(**kwargs)
def user_search(auth=None, **kwargs):
'''
List users
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_list
salt '*' keystoneng.user_list domain_id=b62e76fbeeff4e8fb77073f591cf211e
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_users(**kwargs)
def user_get(auth=None, **kwargs):
'''
Get a single user
CLI Example:
.. code-block:: bash
salt '*' keystoneng.user_get name=user1
salt '*' keystoneng.user_get name=user1 domain_id=b62e76fbeeff4e8fb77073f591cf211e
salt '*' keystoneng.user_get name=02cffaa173b2460f98e40eda3748dae5
'''
cloud = get_openstack_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_user(**kwargs)
def endpoint_create(auth=None, **kwargs):
'''
Create an endpoint
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_create interface=admin service=glance url=https://example.org:9292
salt '*' keystoneng.endpoint_create interface=public service=glance region=RegionOne url=https://example.org:9292
salt '*' keystoneng.endpoint_create interface=admin service=glance url=https://example.org:9292 enabled=True
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_endpoint(**kwargs)
def endpoint_delete(auth=None, **kwargs):
'''
Delete an endpoint
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_delete id=3bee4bd8c2b040ee966adfda1f0bfca9
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_endpoint(**kwargs)
def endpoint_update(auth=None, **kwargs):
'''
Update an endpoint
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_update endpoint_id=4f961ad09d2d48948896bbe7c6a79717 interface=public enabled=False
salt '*' keystoneng.endpoint_update endpoint_id=4f961ad09d2d48948896bbe7c6a79717 region=newregion
salt '*' keystoneng.endpoint_update endpoint_id=4f961ad09d2d48948896bbe7c6a79717 service_name_or_id=glance url=https://example.org:9292
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.update_endpoint(**kwargs)
def endpoint_list(auth=None, **kwargs):
'''
List endpoints
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_list
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_endpoints(**kwargs)
def endpoint_search(auth=None, **kwargs):
'''
Search endpoints
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_search
salt '*' keystoneng.endpoint_search id=02cffaa173b2460f98e40eda3748dae5
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_endpoints(**kwargs)
def endpoint_get(auth=None, **kwargs):
'''
Get a single endpoint
CLI Example:
.. code-block:: bash
salt '*' keystoneng.endpoint_get id=02cffaa173b2460f98e40eda3748dae5
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_endpoint(**kwargs)
def service_create(auth=None, **kwargs):
'''
Create a service
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_create name=glance type=image
salt '*' keystoneng.service_create name=glance type=image description="Image"
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(keep_name=True, **kwargs)
return cloud.create_service(**kwargs)
def service_delete(auth=None, **kwargs):
'''
Delete a service
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_delete name=glance
salt '*' keystoneng.service_delete name=39cc1327cdf744ab815331554430e8ec
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.delete_service(**kwargs)
def service_update(auth=None, **kwargs):
'''
Update a service
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_update name=cinder type=volumev2
salt '*' keystoneng.service_update name=cinder description='new description'
salt '*' keystoneng.service_update name=ab4d35e269f147b3ae2d849f77f5c88f enabled=False
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.update_service(**kwargs)
def service_list(auth=None, **kwargs):
'''
List services
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_list
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_services(**kwargs)
def service_search(auth=None, **kwargs):
'''
Search services
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_search
salt '*' keystoneng.service_search name=glance
salt '*' keystoneng.service_search name=135f0403f8e544dc9008c6739ecda860
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.search_services(**kwargs)
def service_get(auth=None, **kwargs):
'''
Get a single service
CLI Example:
.. code-block:: bash
salt '*' keystoneng.service_get name=glance
salt '*' keystoneng.service_get name=75a5804638944b3ab54f7fbfcec2305a
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.get_service(**kwargs)
def role_assignment_list(auth=None, **kwargs):
'''
List role assignments
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_assignment_list
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.list_role_assignments(**kwargs)
def role_grant(auth=None, **kwargs):
'''
Grant a role in a project/domain to a user/group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_grant name=role1 user=user1 project=project1
salt '*' keystoneng.role_grant name=ddbe3e0ed74e4c7f8027bad4af03339d group=user1 project=project1 domain=domain1
salt '*' keystoneng.role_grant name=ddbe3e0ed74e4c7f8027bad4af03339d group=19573afd5e4241d8b65c42215bae9704 project=1dcac318a83b4610b7a7f7ba01465548
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.grant_role(**kwargs)
def role_revoke(auth=None, **kwargs):
'''
Grant a role in a project/domain to a user/group
CLI Example:
.. code-block:: bash
salt '*' keystoneng.role_revoke name=role1 user=user1 project=project1
salt '*' keystoneng.role_revoke name=ddbe3e0ed74e4c7f8027bad4af03339d group=user1 project=project1 domain=domain1
salt '*' keystoneng.role_revoke name=ddbe3e0ed74e4c7f8027bad4af03339d group=19573afd5e4241d8b65c42215bae9704 project=1dcac318a83b4610b7a7f7ba01465548
'''
cloud = get_operator_cloud(auth)
kwargs = _clean_kwargs(**kwargs)
return cloud.revoke_role(**kwargs)

View File

@ -474,7 +474,7 @@ def sync_returners(saltenv=None, refresh=True, extmod_whitelist=None, extmod_bla
'''
.. versionadded:: 0.10.0
Sync beacons from ``salt://_returners`` to the minion
Sync returners from ``salt://_returners`` to the minion
saltenv
The fileserver environment from which to sync. To sync from more than
@ -585,6 +585,44 @@ def sync_engines(saltenv=None, refresh=False, extmod_whitelist=None, extmod_blac
return ret
def sync_thorium(saltenv=None, refresh=False, extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: Oxygen
Sync Thorium modules from ``salt://_thorium`` to the minion
saltenv
The fileserver environment from which to sync. To sync from more than
one environment, pass a comma-separated list.
If not passed, then all environments configured in the :ref:`top files
<states-top>` will be checked for engines to sync. If no top files are
found, then the ``base`` environment will be synced.
refresh: ``True``
If ``True``, refresh the available execution modules on the minion.
This refresh will be performed even if no new Thorium modules are synced.
Set to ``False`` to prevent this refresh.
extmod_whitelist
comma-seperated list of modules to sync
extmod_blacklist
comma-seperated list of modules to blacklist based on type
CLI Examples:
.. code-block:: bash
salt '*' saltutil.sync_thorium
salt '*' saltutil.sync_thorium saltenv=base,dev
'''
ret = _sync('thorium', saltenv, extmod_whitelist, extmod_blacklist)
if refresh:
refresh_modules()
return ret
def sync_output(saltenv=None, refresh=True, extmod_whitelist=None, extmod_blacklist=None):
'''
Sync outputters from ``salt://_output`` to the minion
@ -628,7 +666,7 @@ def sync_clouds(saltenv=None, refresh=True, extmod_whitelist=None, extmod_blackl
'''
.. versionadded:: 2017.7.0
Sync utility modules from ``salt://_cloud`` to the minion
Sync cloud modules from ``salt://_cloud`` to the minion
saltenv : base
The fileserver environment from which to sync. To sync from more than
@ -864,6 +902,7 @@ def sync_all(saltenv=None, refresh=True, extmod_whitelist=None, extmod_blacklist
ret['log_handlers'] = sync_log_handlers(saltenv, False, extmod_whitelist, extmod_blacklist)
ret['proxymodules'] = sync_proxymodules(saltenv, False, extmod_whitelist, extmod_blacklist)
ret['engines'] = sync_engines(saltenv, False, extmod_whitelist, extmod_blacklist)
ret['thorium'] = sync_thorium(saltenv, False, extmod_whitelist, extmod_blacklist)
if __opts__['file_client'] == 'local':
ret['pillar'] = sync_pillar(saltenv, False, extmod_whitelist, extmod_blacklist)
if refresh:

View File

@ -10,6 +10,7 @@ Module for managing the Salt schedule on a minion
from __future__ import absolute_import
import copy as pycopy
import difflib
import logging
import os
import yaml
@ -23,7 +24,6 @@ from salt.ext import six
__proxyenabled__ = ['*']
import logging
log = logging.getLogger(__name__)
__func_alias__ = {
@ -58,6 +58,7 @@ SCHEDULE_CONF = [
'return_config',
'return_kwargs',
'run_on_start'
'skip_during_range',
]
@ -353,7 +354,7 @@ def build_schedule_item(name, **kwargs):
for item in ['range', 'when', 'once', 'once_fmt', 'cron',
'returner', 'after', 'return_config', 'return_kwargs',
'until', 'run_on_start']:
'until', 'run_on_start', 'skip_during_range']:
if item in kwargs:
schedule[name][item] = kwargs[item]
@ -951,3 +952,191 @@ def copy(name, target, **kwargs):
ret['minions'] = minions
return ret
return ret
def postpone_job(name, current_time, new_time, **kwargs):
'''
Postpone a job in the minion's schedule
Current time and new time should be specified as Unix timestamps
.. versionadded:: Oxygen
CLI Example:
.. code-block:: bash
salt '*' schedule.postpone_job job current_time new_time
'''
ret = {'comment': [],
'result': True}
if not name:
ret['comment'] = 'Job name is required.'
ret['result'] = False
return ret
if not current_time:
ret['comment'] = 'Job current time is required.'
ret['result'] = False
return ret
else:
if not isinstance(current_time, six.integer_types):
ret['comment'] = 'Job current time must be an integer.'
ret['result'] = False
return ret
if not new_time:
ret['comment'] = 'Job new_time is required.'
ret['result'] = False
return ret
else:
if not isinstance(new_time, six.integer_types):
ret['comment'] = 'Job new time must be an integer.'
ret['result'] = False
return ret
if 'test' in __opts__ and __opts__['test']:
ret['comment'] = 'Job: {0} would be postponed in schedule.'.format(name)
else:
if name in list_(show_all=True, where='opts', return_yaml=False):
event_data = {'name': name,
'time': current_time,
'new_time': new_time,
'func': 'postpone_job'}
elif name in list_(show_all=True, where='pillar', return_yaml=False):
event_data = {'name': name,
'time': current_time,
'new_time': new_time,
'where': 'pillar',
'func': 'postpone_job'}
else:
ret['comment'] = 'Job {0} does not exist.'.format(name)
ret['result'] = False
return ret
try:
eventer = salt.utils.event.get_event('minion', opts=__opts__)
res = __salt__['event.fire'](event_data, 'manage_schedule')
if res:
event_ret = eventer.get_event(tag='/salt/minion/minion_schedule_postpone_job_complete', wait=30)
if event_ret and event_ret['complete']:
schedule = event_ret['schedule']
# check item exists in schedule and is enabled
if name in schedule and schedule[name]['enabled']:
ret['result'] = True
ret['comment'] = 'Postponed Job {0} in schedule.'.format(name)
else:
ret['result'] = False
ret['comment'] = 'Failed to postpone job {0} in schedule.'.format(name)
return ret
except KeyError:
# Effectively a no-op, since we can't really return without an event system
ret['comment'] = 'Event module not available. Schedule postpone job failed.'
return ret
def skip_job(name, time, **kwargs):
'''
Skip a job in the minion's schedule at specified time.
Time to skip should be specified as Unix timestamps
.. versionadded:: Oxygen
CLI Example:
.. code-block:: bash
salt '*' schedule.skip_job job time
'''
ret = {'comment': [],
'result': True}
if not name:
ret['comment'] = 'Job name is required.'
ret['result'] = False
if not time:
ret['comment'] = 'Job time is required.'
ret['result'] = False
if 'test' in __opts__ and __opts__['test']:
ret['comment'] = 'Job: {0} would be skipped in schedule.'.format(name)
else:
if name in list_(show_all=True, where='opts', return_yaml=False):
event_data = {'name': name,
'time': time,
'func': 'skip_job'}
elif name in list_(show_all=True, where='pillar', return_yaml=False):
event_data = {'name': name,
'time': time,
'where': 'pillar',
'func': 'skip_job'}
else:
ret['comment'] = 'Job {0} does not exist.'.format(name)
ret['result'] = False
return ret
try:
eventer = salt.utils.event.get_event('minion', opts=__opts__)
res = __salt__['event.fire'](event_data, 'manage_schedule')
if res:
event_ret = eventer.get_event(tag='/salt/minion/minion_schedule_skip_job_complete', wait=30)
if event_ret and event_ret['complete']:
schedule = event_ret['schedule']
# check item exists in schedule and is enabled
if name in schedule and schedule[name]['enabled']:
ret['result'] = True
ret['comment'] = 'Added Skip Job {0} in schedule.'.format(name)
else:
ret['result'] = False
ret['comment'] = 'Failed to skip job {0} in schedule.'.format(name)
return ret
except KeyError:
# Effectively a no-op, since we can't really return without an event system
ret['comment'] = 'Event module not available. Schedule skip job failed.'
return ret
def show_next_fire_time(name, **kwargs):
'''
Show the next fire time for scheduled job
.. versionadded:: Oxygen
CLI Example:
.. code-block:: bash
salt '*' schedule.show_next_fire_time job_name
'''
ret = {'comment': [],
'result': True}
if not name:
ret['comment'] = 'Job name is required.'
ret['result'] = False
try:
event_data = {'name': name, 'func': 'get_next_fire_time'}
eventer = salt.utils.event.get_event('minion', opts=__opts__)
res = __salt__['event.fire'](event_data,
'manage_schedule')
if res:
event_ret = eventer.get_event(tag='/salt/minion/minion_schedule_next_fire_time_complete', wait=30)
except KeyError:
# Effectively a no-op, since we can't really return without an event system
ret = {}
ret['comment'] = 'Event module not available. Schedule show next fire time failed.'
ret['result'] = True
log.debug(ret['comment'])
return ret
return event_ret

View File

@ -43,6 +43,7 @@ from salt.runners.state import orchestrate as _orchestrate
# Import 3rd-party libs
from salt.ext import six
import msgpack
__proxyenabled__ = ['*']
@ -165,6 +166,99 @@ def _snapper_post(opts, jid, pre_num):
log.error('Failed to create snapper pre snapshot for jid: {0}'.format(jid))
def pause(jid, state_id=None, duration=None):
'''
Set up a state id pause, this instructs a running state to pause at a given
state id. This needs to pass in the jid of the running state and can
optionally pass in a duration in seconds. If a state_id is not passed then
the jid referenced will be paused at the begining of the next state run.
The given state id is the id got a given state execution, so given a state
that looks like this:
.. code-block:: yaml
vim:
pkg.installed: []
The state_id to pass to `pause` is `vim`
CLI Examples:
.. code-block:: bash
salt '*' state.pause 20171130110407769519
salt '*' state.pause 20171130110407769519 vim
salt '*' state.pause 20171130110407769519 vim 20
'''
jid = str(jid)
if state_id is None:
state_id = '__all__'
pause_dir = os.path.join(__opts__[u'cachedir'], 'state_pause')
pause_path = os.path.join(pause_dir, jid)
if not os.path.exists(pause_dir):
try:
os.makedirs(pause_dir)
except OSError:
# File created in the gap
pass
data = {}
if os.path.exists(pause_path):
with salt.utils.files.fopen(pause_path, 'rb') as fp_:
data = msgpack.loads(fp_.read())
if state_id not in data:
data[state_id] = {}
if duration:
data[state_id]['duration'] = int(duration)
with salt.utils.files.fopen(pause_path, 'wb') as fp_:
fp_.write(msgpack.dumps(data))
def resume(jid, state_id=None):
'''
Remove a pause from a jid, allowing it to continue. If the state_id is
not specified then the a general pause will be resumed.
The given state_id is the id got a given state execution, so given a state
that looks like this:
.. code-block:: yaml
vim:
pkg.installed: []
The state_id to pass to `rm_pause` is `vim`
CLI Examples:
.. code-block:: bash
salt '*' state.resume 20171130110407769519
salt '*' state.resume 20171130110407769519 vim
'''
jid = str(jid)
if state_id is None:
state_id = '__all__'
pause_dir = os.path.join(__opts__[u'cachedir'], 'state_pause')
pause_path = os.path.join(pause_dir, jid)
if not os.path.exists(pause_dir):
try:
os.makedirs(pause_dir)
except OSError:
# File created in the gap
pass
data = {}
if os.path.exists(pause_path):
with salt.utils.files.fopen(pause_path, 'rb') as fp_:
data = msgpack.loads(fp_.read())
else:
return True
if state_id in data:
data.pop(state_id)
with salt.utils.files.fopen(pause_path, 'wb') as fp_:
fp_.write(msgpack.dumps(data))
def orchestrate(mods,
saltenv='base',
test=None,

View File

@ -1218,41 +1218,55 @@ def mkdir(path,
owner=None,
grant_perms=None,
deny_perms=None,
inheritance=True):
inheritance=True,
reset=False):
'''
Ensure that the directory is available and permissions are set.
Args:
path (str): The full path to the directory.
path (str):
The full path to the directory.
owner (str): The owner of the directory. If not passed, it will be the
account that created the directory, likely SYSTEM
owner (str):
The owner of the directory. If not passed, it will be the account
that created the directory, likely SYSTEM
grant_perms (dict): A dictionary containing the user/group and the basic
permissions to grant, ie: ``{'user': {'perms': 'basic_permission'}}``.
You can also set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to`` setting
like this:
grant_perms (dict):
A dictionary containing the user/group and the basic permissions to
grant, ie: ``{'user': {'perms': 'basic_permission'}}``. You can also
set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to``
setting like this:
.. code-block:: yaml
.. code-block:: yaml
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
To set advanced permissions use a list for the ``perms`` parameter, ie:
To set advanced permissions use a list for the ``perms`` parameter,
ie:
.. code-block:: yaml
.. code-block:: yaml
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
deny_perms (dict): A dictionary containing the user/group and
permissions to deny along with the ``applies_to`` setting. Use the same
format used for the ``grant_perms`` parameter. Remember, deny
permissions supersede grant permissions.
deny_perms (dict):
A dictionary containing the user/group and permissions to deny along
with the ``applies_to`` setting. Use the same format used for the
``grant_perms`` parameter. Remember, deny permissions supersede
grant permissions.
inheritance (bool): If True the object will inherit permissions from the
parent, if False, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created
inheritance (bool):
If True the object will inherit permissions from the parent, if
``False``, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created.
reset (bool):
If ``True`` the existing DACL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded:: Oxygen
Returns:
bool: True if successful
@ -1289,10 +1303,16 @@ def mkdir(path,
# Set owner
if owner:
salt.utils.win_dacl.set_owner(path, owner)
salt.utils.win_dacl.set_owner(obj_name=path, principal=owner)
# Set permissions
set_perms(path, grant_perms, deny_perms, inheritance)
set_perms(
path=path,
grant_perms=grant_perms,
deny_perms=deny_perms,
inheritance=inheritance,
reset=reset)
except WindowsError as exc:
raise CommandExecutionError(exc)
@ -1303,49 +1323,63 @@ def makedirs_(path,
owner=None,
grant_perms=None,
deny_perms=None,
inheritance=True):
inheritance=True,
reset=False):
'''
Ensure that the parent directory containing this path is available.
Args:
path (str): The full path to the directory.
path (str):
The full path to the directory.
owner (str): The owner of the directory. If not passed, it will be the
account that created the directly, likely SYSTEM
.. note::
grant_perms (dict): A dictionary containing the user/group and the basic
permissions to grant, ie: ``{'user': {'perms': 'basic_permission'}}``.
You can also set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to`` setting
like this:
The path must end with a trailing slash otherwise the
directory(s) will be created up to the parent directory. For
example if path is ``C:\\temp\\test``, then it would be treated
as ``C:\\temp\\`` but if the path ends with a trailing slash
like ``C:\\temp\\test\\``, then it would be treated as
``C:\\temp\\test\\``.
.. code-block:: yaml
owner (str):
The owner of the directory. If not passed, it will be the account
that created the directly, likely SYSTEM
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
grant_perms (dict):
A dictionary containing the user/group and the basic permissions to
grant, ie: ``{'user': {'perms': 'basic_permission'}}``. You can also
set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to``
setting like this:
To set advanced permissions use a list for the ``perms`` parameter, ie:
.. code-block:: yaml
.. code-block:: yaml
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
To set advanced permissions use a list for the ``perms`` parameter, ie:
deny_perms (dict): A dictionary containing the user/group and
permissions to deny along with the ``applies_to`` setting. Use the same
format used for the ``grant_perms`` parameter. Remember, deny
permissions supersede grant permissions.
.. code-block:: yaml
inheritance (bool): If True the object will inherit permissions from the
parent, if False, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
.. note::
deny_perms (dict):
A dictionary containing the user/group and permissions to deny along
with the ``applies_to`` setting. Use the same format used for the
``grant_perms`` parameter. Remember, deny permissions supersede
grant permissions.
The path must end with a trailing slash otherwise the directory(s) will
be created up to the parent directory. For example if path is
``C:\\temp\\test``, then it would be treated as ``C:\\temp\\`` but if
the path ends with a trailing slash like ``C:\\temp\\test\\``, then it
would be treated as ``C:\\temp\\test\\``.
inheritance (bool):
If True the object will inherit permissions from the parent, if
False, inheritance will be disabled. Inheritance setting will not
apply to parent directories if they must be created.
reset (bool):
If ``True`` the existing DACL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded:: Oxygen
Returns:
bool: True if successful
@ -1405,7 +1439,13 @@ def makedirs_(path,
for directory_to_create in directories_to_create:
# all directories have the user, group and mode set!!
log.debug('Creating directory: %s', directory_to_create)
mkdir(directory_to_create, owner, grant_perms, deny_perms, inheritance)
mkdir(
path=directory_to_create,
owner=owner,
grant_perms=grant_perms,
deny_perms=deny_perms,
inheritance=inheritance,
reset=reset)
return True
@ -1414,41 +1454,54 @@ def makedirs_perms(path,
owner=None,
grant_perms=None,
deny_perms=None,
inheritance=True):
inheritance=True,
reset=True):
'''
Set owner and permissions for each directory created.
Args:
path (str): The full path to the directory.
path (str):
The full path to the directory.
owner (str): The owner of the directory. If not passed, it will be the
account that created the directory, likely SYSTEM
owner (str):
The owner of the directory. If not passed, it will be the account
that created the directory, likely SYSTEM
grant_perms (dict): A dictionary containing the user/group and the basic
permissions to grant, ie: ``{'user': {'perms': 'basic_permission'}}``.
You can also set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to`` setting
like this:
grant_perms (dict):
A dictionary containing the user/group and the basic permissions to
grant, ie: ``{'user': {'perms': 'basic_permission'}}``. You can also
set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to``
setting like this:
.. code-block:: yaml
.. code-block:: yaml
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
To set advanced permissions use a list for the ``perms`` parameter, ie:
To set advanced permissions use a list for the ``perms`` parameter, ie:
.. code-block:: yaml
.. code-block:: yaml
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
deny_perms (dict): A dictionary containing the user/group and
permissions to deny along with the ``applies_to`` setting. Use the same
format used for the ``grant_perms`` parameter. Remember, deny
permissions supersede grant permissions.
deny_perms (dict):
A dictionary containing the user/group and permissions to deny along
with the ``applies_to`` setting. Use the same format used for the
``grant_perms`` parameter. Remember, deny permissions supersede
grant permissions.
inheritance (bool): If True the object will inherit permissions from the
parent, if False, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created
inheritance (bool):
If ``True`` the object will inherit permissions from the parent, if
``False``, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created
reset (bool):
If ``True`` the existing DACL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded:: Oxygen
Returns:
bool: True if successful, otherwise raise an error
@ -1482,8 +1535,15 @@ def makedirs_perms(path,
try:
# Create the directory here, set inherited True because this is a
# parent directory, the inheritance setting will only apply to the
# child directory
makedirs_perms(head, owner, grant_perms, deny_perms, True)
# target directory. Reset will be False as we only want to reset
# the permissions on the target directory
makedirs_perms(
path=head,
owner=owner,
grant_perms=grant_perms,
deny_perms=deny_perms,
inheritance=True,
reset=False)
except OSError as exc:
# be happy if someone already created the path
if exc.errno != errno.EEXIST:
@ -1492,7 +1552,13 @@ def makedirs_perms(path,
return {}
# Make the directory
mkdir(path, owner, grant_perms, deny_perms, inheritance)
mkdir(
path=path,
owner=owner,
grant_perms=grant_perms,
deny_perms=deny_perms,
inheritance=inheritance,
reset=reset)
return True
@ -1502,66 +1568,64 @@ def check_perms(path,
owner=None,
grant_perms=None,
deny_perms=None,
inheritance=True):
inheritance=True,
reset=False):
'''
Set owner and permissions for each directory created.
Check owner and permissions for the passed directory. This function checks
the permissions and sets them, returning the changes made.
Args:
path (str): The full path to the directory.
path (str):
The full path to the directory.
ret (dict): A dictionary to append changes to and return. If not passed,
will create a new dictionary to return.
ret (dict):
A dictionary to append changes to and return. If not passed, will
create a new dictionary to return.
owner (str): The owner of the directory. If not passed, it will be the
account that created the directory, likely SYSTEM
owner (str):
The owner to set for the directory.
grant_perms (dict): A dictionary containing the user/group and the basic
permissions to grant, ie: ``{'user': {'perms': 'basic_permission'}}``.
You can also set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to`` setting
like this:
grant_perms (dict):
A dictionary containing the user/group and the basic permissions to
check/grant, ie: ``{'user': {'perms': 'basic_permission'}}``.
Default is ``None``.
.. code-block:: yaml
deny_perms (dict):
A dictionary containing the user/group and permissions to
check/deny. Default is ``None``.
{'user': {'perms': 'full_control', 'applies_to': 'this_folder'}}
inheritance (bool):
``True will check if inheritance is enabled and enable it. ``False``
will check if inheritance is disabled and disable it. Defaultl is
``True``.
To set advanced permissions use a list for the ``perms`` parameter, ie:
.. code-block:: yaml
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
deny_perms (dict): A dictionary containing the user/group and
permissions to deny along with the ``applies_to`` setting. Use the same
format used for the ``grant_perms`` parameter. Remember, deny
permissions supersede grant permissions.
inheritance (bool): If True the object will inherit permissions from the
parent, if False, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created
reset (bool):
``True`` wil show what permisisons will be removed by resetting the
DACL. ``False`` will do nothing. Default is ``False``.
Returns:
bool: True if successful, otherwise raise an error
dict: A dictionary of changes that have been made
CLI Example:
.. code-block:: bash
# To grant the 'Users' group 'read & execute' permissions.
salt '*' file.check_perms C:\\Temp\\ Administrators "{'Users': {'perms': 'read_execute'}}"
# To see changes to ``C:\\Temp`` if the 'Users' group is given 'read & execute' permissions.
salt '*' file.check_perms C:\\Temp\\ {} Administrators "{'Users': {'perms': 'read_execute'}}"
# Locally using salt call
salt-call file.check_perms C:\\Temp\\ Administrators "{'Users': {'perms': 'read_execute', 'applies_to': 'this_folder_only'}}"
salt-call file.check_perms C:\\Temp\\ {} Administrators "{'Users': {'perms': 'read_execute', 'applies_to': 'this_folder_only'}}"
# Specify advanced attributes with a list
salt '*' file.check_perms C:\\Temp\\ Administrators "{'jsnuffy': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'files_only'}}"
salt '*' file.check_perms C:\\Temp\\ {} Administrators "{'jsnuffy': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'files_only'}}"
'''
path = os.path.expanduser(path)
if not ret:
ret = {'name': path,
'changes': {},
'pchanges': {},
'comment': [],
'result': True}
orig_comment = ''
@ -1571,14 +1635,16 @@ def check_perms(path,
# Check owner
if owner:
owner = salt.utils.win_dacl.get_name(owner)
current_owner = salt.utils.win_dacl.get_owner(path)
owner = salt.utils.win_dacl.get_name(principal=owner)
current_owner = salt.utils.win_dacl.get_owner(obj_name=path)
if owner != current_owner:
if __opts__['test'] is True:
ret['pchanges']['owner'] = owner
else:
try:
salt.utils.win_dacl.set_owner(path, owner)
salt.utils.win_dacl.set_owner(
obj_name=path,
principal=owner)
ret['changes']['owner'] = owner
except CommandExecutionError:
ret['result'] = False
@ -1586,7 +1652,7 @@ def check_perms(path,
'Failed to change owner to "{0}"'.format(owner))
# Check permissions
cur_perms = salt.utils.win_dacl.get_permissions(path)
cur_perms = salt.utils.win_dacl.get_permissions(obj_name=path)
# Verify Deny Permissions
changes = {}
@ -1594,7 +1660,7 @@ def check_perms(path,
for user in deny_perms:
# Check that user exists:
try:
user_name = salt.utils.win_dacl.get_name(user)
user_name = salt.utils.win_dacl.get_name(principal=user)
except CommandExecutionError:
ret['comment'].append(
'Deny Perms: User "{0}" missing from Target System'.format(user))
@ -1619,7 +1685,11 @@ def check_perms(path,
# Check Perms
if isinstance(deny_perms[user]['perms'], six.string_types):
if not salt.utils.win_dacl.has_permission(
path, user, deny_perms[user]['perms'], 'deny'):
obj_name=path,
principal=user,
permission=deny_perms[user]['perms'],
access_mode='deny',
exact=False):
changes[user] = {'perms': deny_perms[user]['perms']}
else:
for perm in deny_perms[user]['perms']:
@ -1640,9 +1710,10 @@ def check_perms(path,
changes[user]['applies_to'] = applies_to
if changes:
ret['pchanges']['deny_perms'] = {}
ret['changes']['deny_perms'] = {}
for user in changes:
user_name = salt.utils.win_dacl.get_name(user)
user_name = salt.utils.win_dacl.get_name(principal=user)
if __opts__['test'] is True:
ret['pchanges']['deny_perms'][user] = changes[user]
@ -1689,7 +1760,11 @@ def check_perms(path,
try:
salt.utils.win_dacl.set_permissions(
path, user, perms, 'deny', applies_to)
obj_name=path,
principal=user,
permissions=perms,
access_mode='deny',
applies_to=applies_to)
ret['changes']['deny_perms'][user] = changes[user]
except CommandExecutionError:
ret['result'] = False
@ -1703,7 +1778,7 @@ def check_perms(path,
for user in grant_perms:
# Check that user exists:
try:
user_name = salt.utils.win_dacl.get_name(user)
user_name = salt.utils.win_dacl.get_name(principal=user)
except CommandExecutionError:
ret['comment'].append(
'Grant Perms: User "{0}" missing from Target System'.format(user))
@ -1729,12 +1804,19 @@ def check_perms(path,
# Check Perms
if isinstance(grant_perms[user]['perms'], six.string_types):
if not salt.utils.win_dacl.has_permission(
path, user, grant_perms[user]['perms']):
obj_name=path,
principal=user,
permission=grant_perms[user]['perms'],
access_mode='grant'):
changes[user] = {'perms': grant_perms[user]['perms']}
else:
for perm in grant_perms[user]['perms']:
if not salt.utils.win_dacl.has_permission(
path, user, perm, exact=False):
obj_name=path,
principal=user,
permission=perm,
access_mode='grant',
exact=False):
if user not in changes:
changes[user] = {'perms': []}
changes[user]['perms'].append(grant_perms[user]['perms'])
@ -1750,11 +1832,12 @@ def check_perms(path,
changes[user]['applies_to'] = applies_to
if changes:
ret['pchanges']['grant_perms'] = {}
ret['changes']['grant_perms'] = {}
for user in changes:
user_name = salt.utils.win_dacl.get_name(user)
user_name = salt.utils.win_dacl.get_name(principal=user)
if __opts__['test'] is True:
ret['changes']['grant_perms'][user] = changes[user]
ret['pchanges']['grant_perms'][user] = changes[user]
else:
applies_to = None
if 'applies_to' not in changes[user]:
@ -1796,7 +1879,11 @@ def check_perms(path,
try:
salt.utils.win_dacl.set_permissions(
path, user, perms, 'grant', applies_to)
obj_name=path,
principal=user,
permissions=perms,
access_mode='grant',
applies_to=applies_to)
ret['changes']['grant_perms'][user] = changes[user]
except CommandExecutionError:
ret['result'] = False
@ -1806,12 +1893,14 @@ def check_perms(path,
# Check inheritance
if inheritance is not None:
if not inheritance == salt.utils.win_dacl.get_inheritance(path):
if not inheritance == salt.utils.win_dacl.get_inheritance(obj_name=path):
if __opts__['test'] is True:
ret['changes']['inheritance'] = inheritance
ret['pchanges']['inheritance'] = inheritance
else:
try:
salt.utils.win_dacl.set_inheritance(path, inheritance)
salt.utils.win_dacl.set_inheritance(
obj_name=path,
enabled=inheritance)
ret['changes']['inheritance'] = inheritance
except CommandExecutionError:
ret['result'] = False
@ -1819,6 +1908,45 @@ def check_perms(path,
'Failed to set inheritance for "{0}" to '
'{1}'.format(path, inheritance))
# Check reset
# If reset=True, which users will be removed as a result
if reset:
for user_name in cur_perms:
if user_name not in grant_perms:
if 'grant' in cur_perms[user_name] and not \
cur_perms[user_name]['grant']['inherited']:
if __opts__['test'] is True:
if 'remove_perms' not in ret['pchanges']:
ret['pchanges']['remove_perms'] = {}
ret['pchanges']['remove_perms'].update(
{user_name: cur_perms[user_name]})
else:
if 'remove_perms' not in ret['changes']:
ret['changes']['remove_perms'] = {}
salt.utils.win_dacl.rm_permissions(
obj_name=path,
principal=user_name,
ace_type='grant')
ret['changes']['remove_perms'].update(
{user_name: cur_perms[user_name]})
if user_name not in deny_perms:
if 'deny' in cur_perms[user_name] and not \
cur_perms[user_name]['deny']['inherited']:
if __opts__['test'] is True:
if 'remove_perms' not in ret['pchanges']:
ret['pchanges']['remove_perms'] = {}
ret['pchanges']['remove_perms'].update(
{user_name: cur_perms[user_name]})
else:
if 'remove_perms' not in ret['changes']:
ret['changes']['remove_perms'] = {}
salt.utils.win_dacl.rm_permissions(
obj_name=path,
principal=user_name,
ace_type='deny')
ret['changes']['remove_perms'].update(
{user_name: cur_perms[user_name]})
# Re-add the Original Comment if defined
if isinstance(orig_comment, six.string_types):
if orig_comment:
@ -1830,25 +1958,30 @@ def check_perms(path,
ret['comment'] = '\n'.join(ret['comment'])
# Set result for test = True
if __opts__['test'] is True and ret['changes']:
if __opts__['test'] and (ret['changes'] or ret['pchanges']):
ret['result'] = None
return ret
def set_perms(path, grant_perms=None, deny_perms=None, inheritance=True):
def set_perms(path,
grant_perms=None,
deny_perms=None,
inheritance=True,
reset=False):
'''
Set permissions for the given path
Args:
path (str): The full path to the directory.
path (str):
The full path to the directory.
grant_perms (dict):
A dictionary containing the user/group and the basic permissions to
grant, ie: ``{'user': {'perms': 'basic_permission'}}``. You can also
set the ``applies_to`` setting here. The default is
``this_folder_subfolders_files``. Specify another ``applies_to``
set the ``applies_to`` setting here. The default for ``applise_to``
is ``this_folder_subfolders_files``. Specify another ``applies_to``
setting like this:
.. code-block:: yaml
@ -1863,7 +1996,10 @@ def set_perms(path, grant_perms=None, deny_perms=None, inheritance=True):
{'user': {'perms': ['read_attributes', 'read_ea'], 'applies_to': 'this_folder'}}
To see a list of available attributes and applies to settings see
the documentation for salt.utils.win_dacl
the documentation for salt.utils.win_dacl.
A value of ``None`` will make no changes to the ``grant`` portion of
the DACL. Default is ``None``.
deny_perms (dict):
A dictionary containing the user/group and permissions to deny along
@ -1871,13 +2007,27 @@ def set_perms(path, grant_perms=None, deny_perms=None, inheritance=True):
``grant_perms`` parameter. Remember, deny permissions supersede
grant permissions.
A value of ``None`` will make no changes to the ``deny`` portion of
the DACL. Default is ``None``.
inheritance (bool):
If True the object will inherit permissions from the parent, if
False, inheritance will be disabled. Inheritance setting will not
apply to parent directories if they must be created
If ``True`` the object will inherit permissions from the parent, if
``False``, inheritance will be disabled. Inheritance setting will
not apply to parent directories if they must be created. Default is
``False``.
reset (bool):
If ``True`` the existing DCL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded: Oxygen
Returns:
bool: True if successful, otherwise raise an error
bool: True if successful
Raises:
CommandExecutionError: If unsuccessful
CLI Example:
@ -1894,11 +2044,19 @@ def set_perms(path, grant_perms=None, deny_perms=None, inheritance=True):
'''
ret = {}
# Get the DACL for the directory
dacl = salt.utils.win_dacl.dacl(path)
if reset:
# Get an empty DACL
dacl = salt.utils.win_dacl.dacl()
# Get current file/folder permissions
cur_perms = salt.utils.win_dacl.get_permissions(path)
# Get an empty perms dict
cur_perms = {}
else:
# Get the DACL for the directory
dacl = salt.utils.win_dacl.dacl(path)
# Get current file/folder permissions
cur_perms = salt.utils.win_dacl.get_permissions(path)
# Set 'deny' perms if any
if deny_perms is not None:

View File

@ -279,11 +279,23 @@ def _get_extra_options(**kwargs):
'''
ret = []
kwargs = salt.utils.args.clean_kwargs(**kwargs)
# Remove already handled options from kwargs
fromrepo = kwargs.pop('fromrepo', '')
repo = kwargs.pop('repo', '')
disablerepo = kwargs.pop('disablerepo', '')
enablerepo = kwargs.pop('enablerepo', '')
disable_excludes = kwargs.pop('disableexcludes', '')
branch = kwargs.pop('branch', '')
for key, value in six.iteritems(kwargs):
if isinstance(key, six.string_types):
if isinstance(value, six.string_types):
log.info('Adding extra option --%s=\'%s\'', key, value)
ret.append('--{0}=\'{1}\''.format(key, value))
elif value is True:
log.info('Adding extra option --%s', key)
ret.append('--{0}'.format(key))
log.info('Adding extra options %s', ret)
return ret

View File

@ -399,6 +399,14 @@ def _systemd_scope():
and __salt__['config.get']('systemd.scope', True)
def _clean_cache():
'''
Clean cached results
'''
for cache_name in ['pkg.list_pkgs', 'pkg.list_provides']:
__context__.pop(cache_name, None)
def list_upgrades(refresh=True, **kwargs):
'''
List all available package upgrades on this system
@ -1049,6 +1057,10 @@ def install(name=None,
operator (<, >, <=, >=, =) and a version number (ex. '>1.2.3-4').
This parameter is ignored if ``pkgs`` or ``sources`` is passed.
resolve_capabilities
If this option is set to True zypper will take capabilites into
account. In this case names which are just provided by a package
will get installed. Default is False.
Multiple Package Installation Options:
@ -1164,7 +1176,10 @@ def install(name=None,
log.info('Targeting repo \'{0}\''.format(fromrepo))
else:
fromrepoopt = ''
cmd_install = ['install', '--name', '--auto-agree-with-licenses']
cmd_install = ['install', '--auto-agree-with-licenses']
cmd_install.append(kwargs.get('resolve_capabilities') and '--capability' or '--name')
if not refresh:
cmd_install.insert(0, '--no-refresh')
if skip_verify:
@ -1194,7 +1209,7 @@ def install(name=None,
downgrades = downgrades[500:]
__zypper__(no_repo_failure=ignore_repo_failure).call(*cmd)
__context__.pop('pkg.list_pkgs', None)
_clean_cache()
new = list_pkgs(attr=diff_attr) if not downloadonly else list_downloaded()
# Handle packages which report multiple new versions
@ -1311,7 +1326,7 @@ def upgrade(refresh=True,
old = list_pkgs()
__zypper__(systemd_scope=_systemd_scope()).noraise.call(*cmd_update)
__context__.pop('pkg.list_pkgs', None)
_clean_cache()
new = list_pkgs()
# Handle packages which report multiple new versions
@ -1360,7 +1375,7 @@ def _uninstall(name=None, pkgs=None):
__zypper__(systemd_scope=systemd_scope).call('remove', *targets[:500])
targets = targets[500:]
__context__.pop('pkg.list_pkgs', None)
_clean_cache()
ret = salt.utils.data.compare_dicts(old, list_pkgs())
if errors:
@ -1750,7 +1765,7 @@ def list_installed_patterns():
return _get_patterns(installed_only=True)
def search(criteria, refresh=False):
def search(criteria, refresh=False, **kwargs):
'''
List known packags, available to the system.
@ -1759,26 +1774,94 @@ def search(criteria, refresh=False):
If set to False (default) it depends on zypper if a refresh is
executed.
match (str)
One of `exact`, `words`, `substrings`. Search for an `exact` match
or for the whole `words` only. Default to `substrings` to patch
partial words.
provides (bool)
Search for packages which provide the search strings.
recommends (bool)
Search for packages which recommend the search strings.
requires (bool)
Search for packages which require the search strings.
suggests (bool)
Search for packages which suggest the search strings.
conflicts (bool)
Search packages conflicting with search strings.
obsoletes (bool)
Search for packages which obsolete the search strings.
file_list (bool)
Search for a match in the file list of packages.
search_descriptions (bool)
Search also in package summaries and descriptions.
case_sensitive (bool)
Perform case-sensitive search.
installed_only (bool)
Show only installed packages.
not_installed_only (bool)
Show only packages which are not installed.
details (bool)
Show version and repository
CLI Examples:
.. code-block:: bash
salt '*' pkg.search <criteria>
'''
ALLOWED_SEARCH_OPTIONS = {
'provides': '--provides',
'recommends': '--recommends',
'requires': '--requires',
'suggests': '--suggests',
'conflicts': '--conflicts',
'obsoletes': '--obsoletes',
'file_list': '--file-list',
'search_descriptions': '--search-descriptions',
'case_sensitive': '--case-sensitive',
'installed_only': '--installed-only',
'not_installed_only': '-u',
'details': '--details'
}
if refresh:
refresh_db()
solvables = __zypper__.nolock.xml.call('se', criteria).getElementsByTagName('solvable')
cmd = ['search']
if kwargs.get('match') == 'exact':
cmd.append('--match-exact')
elif kwargs.get('match') == 'words':
cmd.append('--match-words')
elif kwargs.get('match') == 'substrings':
cmd.append('--match-substrings')
for opt in kwargs:
if opt in ALLOWED_SEARCH_OPTIONS:
cmd.append(ALLOWED_SEARCH_OPTIONS.get(opt))
cmd.append(criteria)
solvables = __zypper__.nolock.noraise.xml.call(*cmd).getElementsByTagName('solvable')
if not solvables:
raise CommandExecutionError(
'No packages found matching \'{0}\''.format(criteria)
)
out = {}
for solvable in [slv for slv in solvables
if slv.getAttribute('status') == 'not-installed'
and slv.getAttribute('kind') == 'package']:
out[solvable.getAttribute('name')] = {'summary': solvable.getAttribute('summary')}
for solvable in solvables:
out[solvable.getAttribute('name')] = dict()
for k, v in solvable.attributes.items():
out[solvable.getAttribute('name')][k] = v
return out
@ -2033,3 +2116,97 @@ def list_installed_patches():
salt '*' pkg.list_installed_patches
'''
return _get_patches(installed_only=True)
def list_provides(**kwargs):
'''
.. versionadded:: Oxygen
List package provides of installed packages as a dict.
{'<provided_name>': ['<package_name>', '<package_name>', ...]}
CLI Examples:
.. code-block:: bash
salt '*' pkg.list_provides
'''
ret = __context__.get('pkg.list_provides')
if not ret:
cmd = ['rpm', '-qa', '--queryformat', '[%{PROVIDES}_|-%{NAME}\n]']
ret = dict()
for line in __salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False).splitlines():
provide, realname = line.split('_|-')
if provide == realname:
continue
if provide not in ret:
ret[provide] = list()
ret[provide].append(realname)
__context__['pkg.list_provides'] = ret
return ret
def resolve_capabilities(pkgs, refresh, **kwargs):
'''
.. versionadded:: Oxygen
Convert name provides in ``pkgs`` into real package names if
``resolve_capabilities`` parameter is set to True. In case of
``resolve_capabilities`` is set to False the package list
is returned unchanged.
refresh
force a refresh if set to True.
If set to False (default) it depends on zypper if a refresh is
executed.
resolve_capabilities
If this option is set to True the input will be checked if
a package with this name exists. If not, this function will
search for a package which provides this name. If one is found
the output is exchanged with the real package name.
In case this option is set to False (Default) the input will
be returned unchanged.
CLI Examples:
.. code-block:: bash
salt '*' pkg.resolve_capabilities resolve_capabilities=True w3m_ssl
'''
if refresh:
refresh_db()
ret = list()
for pkg in pkgs:
if isinstance(pkg, dict):
name = next(iter(pkg))
version = pkg[name]
else:
name = pkg
version = None
if kwargs.get('resolve_capabilities', False):
try:
search(name, match='exact')
except CommandExecutionError:
# no package this such a name found
# search for a package which provides this name
try:
result = search(name, provides=True, match='exact')
if len(result) == 1:
name = result.keys()[0]
elif len(result) > 1:
log.warn("Found ambiguous match for capability '{0}'.".format(pkg))
except CommandExecutionError as exc:
# when search throws an exception stay with original name and version
log.debug("Search failed with: {0}".format(exc))
if version:
ret.append({name: version})
else:
ret.append(name)
return ret

View File

@ -294,9 +294,11 @@ def get_load(jid):
if not os.path.exists(jid_dir) or not os.path.exists(load_fn):
return {}
serial = salt.payload.Serial(__opts__)
ret = {}
with salt.utils.files.fopen(os.path.join(jid_dir, LOAD_P), 'rb') as rfh:
ret = serial.load(rfh)
if ret is None:
ret = {}
minions_cache = [os.path.join(jid_dir, MINIONS_P)]
minions_cache.extend(
glob.glob(os.path.join(jid_dir, SYNDIC_MINIONS_P.format('*')))

View File

@ -52,6 +52,7 @@ def sync_all(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
ret['runners'] = sync_runners(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['wheel'] = sync_wheel(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['engines'] = sync_engines(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['thorium'] = sync_thorium(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['queues'] = sync_queues(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['pillar'] = sync_pillar(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
ret['utils'] = sync_utils(saltenv=saltenv, extmod_whitelist=extmod_whitelist, extmod_blacklist=extmod_blacklist)
@ -303,6 +304,32 @@ def sync_engines(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
extmod_blacklist=extmod_blacklist)[0]
def sync_thorium(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: Oxygen
Sync Thorium from ``salt://_thorium`` to the master
saltenv: ``base``
The fileserver environment from which to sync. To sync from more than
one environment, pass a comma-separated list.
extmod_whitelist
comma-seperated list of modules to sync
extmod_blacklist
comma-seperated list of modules to blacklist based on type
CLI Example:
.. code-block:: bash
salt-run saltutil.sync_thorium
'''
return salt.utils.extmods.sync(__opts__, 'thorium', saltenv=saltenv, extmod_whitelist=extmod_whitelist,
extmod_blacklist=extmod_blacklist)[0]
def sync_queues(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
Sync queue modules from ``salt://_queues`` to the master
@ -381,7 +408,7 @@ def sync_sdb(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: 2017.7.0
Sync utils modules from ``salt://_sdb`` to the master
Sync sdb modules from ``salt://_sdb`` to the master
saltenv : base
The fileserver environment from which to sync. To sync from more than
@ -427,7 +454,7 @@ def sync_cache(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: 2017.7.0
Sync utils modules from ``salt://_cache`` to the master
Sync cache modules from ``salt://_cache`` to the master
saltenv : base
The fileserver environment from which to sync. To sync from more than
@ -453,7 +480,7 @@ def sync_fileserver(saltenv='base', extmod_whitelist=None, extmod_blacklist=None
'''
.. versionadded:: Oxygen
Sync utils modules from ``salt://_fileserver`` to the master
Sync fileserver modules from ``salt://_fileserver`` to the master
saltenv : base
The fileserver environment from which to sync. To sync from more than
@ -479,7 +506,7 @@ def sync_clouds(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: 2017.7.0
Sync utils modules from ``salt://_clouds`` to the master
Sync cloud modules from ``salt://_clouds`` to the master
saltenv : base
The fileserver environment from which to sync. To sync from more than
@ -505,7 +532,7 @@ def sync_roster(saltenv='base', extmod_whitelist=None, extmod_blacklist=None):
'''
.. versionadded:: 2017.7.0
Sync utils modules from ``salt://_roster`` to the master
Sync roster modules from ``salt://_roster`` to the master
saltenv : base
The fileserver environment from which to sync. To sync from more than

View File

@ -22,7 +22,7 @@ def get(uri):
.. code-block:: bash
salt '*' sdb.get sdb://mymemcached/foo
salt-run sdb.get sdb://mymemcached/foo
'''
return salt.utils.sdb.sdb_get(uri, __opts__, __utils__)
@ -37,7 +37,7 @@ def set_(uri, value):
.. code-block:: bash
salt '*' sdb.set sdb://mymemcached/foo bar
salt-run sdb.set sdb://mymemcached/foo bar
'''
return salt.utils.sdb.sdb_set(uri, value, __opts__, __utils__)
@ -52,7 +52,7 @@ def delete(uri):
.. code-block:: bash
salt '*' sdb.delete sdb://mymemcached/foo
salt-run sdb.delete sdb://mymemcached/foo
'''
return salt.utils.sdb.sdb_delete(uri, __opts__, __utils__)

View File

@ -15,6 +15,24 @@ from salt.exceptions import SaltInvocationError
LOGGER = logging.getLogger(__name__)
def set_pause(jid, state_id, duration=None):
'''
Set up a state id pause, this instructs a running state to pause at a given
state id. This needs to pass in the jid of the running state and can
optionally pass in a duration in seconds.
'''
minion = salt.minion.MasterMinion(__opts__)
minion['state.set_pause'](jid, state_id, duration)
def rm_pause(jid, state_id, duration=None):
'''
Remove a pause from a jid, allowing it to continue
'''
minion = salt.minion.MasterMinion(__opts__)
minion['state.rm_pause'](jid, state_id)
def orchestrate(mods,
saltenv='base',
test=None,

View File

@ -1918,6 +1918,8 @@ class State(object):
if self.mocked:
ret = mock_ret(cdata)
else:
# Check if this low chunk is paused
self.check_pause(low)
# Execute the state function
if not low.get(u'__prereq__') and low.get(u'parallel'):
# run the state call in parallel, but only if not in a prereq
@ -2127,6 +2129,48 @@ class State(object):
return not running[tag][u'result']
return False
def check_pause(self, low):
'''
Check to see if this low chunk has been paused
'''
if not self.jid:
# Can't pause on salt-ssh since we can't track continuous state
return
pause_path = os.path.join(self.opts[u'cachedir'], 'state_pause', self.jid)
start = time.time()
if os.path.isfile(pause_path):
try:
while True:
tries = 0
with salt.utils.files.fopen(pause_path, 'rb') as fp_:
try:
pdat = msgpack.loads(fp_.read())
except msgpack.UnpackValueError:
# Reading race condition
if tries > 10:
# Break out if there are a ton of read errors
return
tries += 1
time.sleep(1)
continue
id_ = low[u'__id__']
key = u''
if id_ in pdat:
key = id_
elif u'__all__' in pdat:
key = u'__all__'
if key:
if u'duration' in pdat[key]:
now = time.time()
if now - start > pdat[key][u'duration']:
return
else:
return
time.sleep(1)
except Exception as exc:
log.error('Failed to read in pause data for file located at: %s', pause_path)
return
def reconcile_procs(self, running):
'''
Check the running dict for processes and resolve them
@ -2682,6 +2726,14 @@ class State(object):
except OSError:
log.debug(u'File %s does not exist, no need to cleanup', accum_data_path)
_cleanup_accumulator_data()
if self.jid is not None:
pause_path = os.path.join(self.opts[u'cachedir'], u'state_pause', self.jid)
if os.path.isfile(pause_path):
try:
os.remove(pause_path)
except OSError:
# File is not present, all is well
pass
return ret

View File

@ -761,7 +761,8 @@ def _check_directory_win(name,
win_owner,
win_perms=None,
win_deny_perms=None,
win_inheritance=None):
win_inheritance=None,
win_perms_reset=None):
'''
Check what changes need to be made on a directory
'''
@ -879,6 +880,20 @@ def _check_directory_win(name,
if not win_inheritance == salt.utils.win_dacl.get_inheritance(name):
changes['inheritance'] = win_inheritance
# Check reset
if win_perms_reset:
for user_name in perms:
if user_name not in win_perms:
if 'grant' in perms[user_name] and not perms[user_name]['grant']['inherited']:
if 'remove_perms' not in changes:
changes['remove_perms'] = {}
changes['remove_perms'].update({user_name: perms[user_name]})
if user_name not in win_deny_perms:
if 'deny' in perms[user_name] and not perms[user_name]['deny']['inherited']:
if 'remove_perms' not in changes:
changes['remove_perms'] = {}
changes['remove_perms'].update({user_name: perms[user_name]})
if changes:
return None, 'The directory "{0}" will be changed'.format(name), changes
@ -1566,6 +1581,7 @@ def managed(name,
win_perms=None,
win_deny_perms=None,
win_inheritance=True,
win_perms_reset=False,
**kwargs):
r'''
Manage a given file, this function allows for a file to be downloaded from
@ -2072,6 +2088,13 @@ def managed(name,
.. versionadded:: 2017.7.0
win_perms_reset : False
If ``True`` the existing DACL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded:: Oxygen
Here's an example using the above ``win_*`` parameters:
.. code-block:: yaml
@ -2314,8 +2337,13 @@ def managed(name,
# Check and set the permissions if necessary
if salt.utils.platform.is_windows():
ret = __salt__['file.check_perms'](
name, ret, win_owner, win_perms, win_deny_perms, None,
win_inheritance)
path=name,
ret=ret,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
ret, _ = __salt__['file.check_perms'](
name, ret, user, group, mode, attrs, follow_symlinks)
@ -2356,8 +2384,13 @@ def managed(name,
if salt.utils.platform.is_windows():
ret = __salt__['file.check_perms'](
name, ret, win_owner, win_perms, win_deny_perms, None,
win_inheritance)
path=name,
ret=ret,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
if isinstance(ret['pchanges'], tuple):
ret['result'], ret['comment'] = ret['pchanges']
@ -2448,6 +2481,7 @@ def managed(name,
win_perms=win_perms,
win_deny_perms=win_deny_perms,
win_inheritance=win_inheritance,
win_perms_reset=win_perms_reset,
encoding=encoding,
encoding_errors=encoding_errors,
**kwargs)
@ -2517,6 +2551,7 @@ def managed(name,
win_perms=win_perms,
win_deny_perms=win_deny_perms,
win_inheritance=win_inheritance,
win_perms_reset=win_perms_reset,
encoding=encoding,
encoding_errors=encoding_errors,
**kwargs)
@ -2590,6 +2625,7 @@ def directory(name,
win_perms=None,
win_deny_perms=None,
win_inheritance=True,
win_perms_reset=False,
**kwargs):
r'''
Ensure that a named directory is present and has the right perms
@ -2751,6 +2787,13 @@ def directory(name,
.. versionadded:: 2017.7.0
win_perms_reset : False
If ``True`` the existing DACL will be cleared and replaced with the
settings defined in this function. If ``False``, new entries will be
appended to the existing DACL. Default is ``False``.
.. versionadded:: Oxygen
Here's an example using the above ``win_*`` parameters:
.. code-block:: yaml
@ -2855,13 +2898,23 @@ def directory(name,
elif force:
# Remove whatever is in the way
if os.path.isfile(name):
os.remove(name)
ret['changes']['forced'] = 'File was forcibly replaced'
if __opts__['test']:
ret['pchanges']['forced'] = 'File was forcibly replaced'
else:
os.remove(name)
ret['changes']['forced'] = 'File was forcibly replaced'
elif __salt__['file.is_link'](name):
__salt__['file.remove'](name)
ret['changes']['forced'] = 'Symlink was forcibly replaced'
if __opts__['test']:
ret['pchanges']['forced'] = 'Symlink was forcibly replaced'
else:
__salt__['file.remove'](name)
ret['changes']['forced'] = 'Symlink was forcibly replaced'
else:
__salt__['file.remove'](name)
if __opts__['test']:
ret['pchanges']['forced'] = 'Directory was forcibly replaced'
else:
__salt__['file.remove'](name)
ret['changes']['forced'] = 'Directory was forcibly replaced'
else:
if os.path.isfile(name):
return _error(
@ -2874,17 +2927,26 @@ def directory(name,
# Check directory?
if salt.utils.platform.is_windows():
presult, pcomment, ret['pchanges'] = _check_directory_win(
name, win_owner, win_perms, win_deny_perms, win_inheritance)
presult, pcomment, pchanges = _check_directory_win(
name=name,
win_owner=win_owner,
win_perms=win_perms,
win_deny_perms=win_deny_perms,
win_inheritance=win_inheritance,
win_perms_reset=win_perms_reset)
else:
presult, pcomment, ret['pchanges'] = _check_directory(
presult, pcomment, pchanges = _check_directory(
name, user, group, recurse or [], dir_mode, clean, require,
exclude_pat, max_depth, follow_symlinks)
if __opts__['test']:
if pchanges:
ret['pchanges'].update(pchanges)
# Don't run through the reset of the function if there are no changes to be
# made
if not ret['pchanges'] or __opts__['test']:
ret['result'] = presult
ret['comment'] = pcomment
ret['changes'] = ret['pchanges']
return ret
if not os.path.isdir(name):
@ -2900,8 +2962,13 @@ def directory(name,
if not os.path.isdir(drive):
return _error(
ret, 'Drive {0} is not mapped'.format(drive))
__salt__['file.makedirs'](name, win_owner, win_perms,
win_deny_perms, win_inheritance)
__salt__['file.makedirs'](
path=name,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
__salt__['file.makedirs'](name, user=user, group=group,
mode=dir_mode)
@ -2910,8 +2977,13 @@ def directory(name,
ret, 'No directory to create {0} in'.format(name))
if salt.utils.platform.is_windows():
__salt__['file.mkdir'](name, win_owner, win_perms, win_deny_perms,
win_inheritance)
__salt__['file.mkdir'](
path=name,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
__salt__['file.mkdir'](name, user=user, group=group, mode=dir_mode)
@ -2925,7 +2997,13 @@ def directory(name,
if not children_only:
if salt.utils.platform.is_windows():
ret = __salt__['file.check_perms'](
name, ret, win_owner, win_perms, win_deny_perms, None, win_inheritance)
path=name,
ret=ret,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
ret, perms = __salt__['file.check_perms'](
name, ret, user, group, dir_mode, None, follow_symlinks)
@ -2996,8 +3074,13 @@ def directory(name,
try:
if salt.utils.platform.is_windows():
ret = __salt__['file.check_perms'](
full, ret, win_owner, win_perms, win_deny_perms, None,
win_inheritance)
path=full,
ret=ret,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
ret, _ = __salt__['file.check_perms'](
full, ret, user, group, file_mode, None, follow_symlinks)
@ -3011,8 +3094,13 @@ def directory(name,
try:
if salt.utils.platform.is_windows():
ret = __salt__['file.check_perms'](
full, ret, win_owner, win_perms, win_deny_perms, None,
win_inheritance)
path=full,
ret=ret,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance,
reset=win_perms_reset)
else:
ret, _ = __salt__['file.check_perms'](
full, ret, user, group, dir_mode, None, follow_symlinks)
@ -3034,7 +3122,8 @@ def directory(name,
if children_only:
ret['comment'] = u'Directory {0}/* updated'.format(name)
else:
ret['comment'] = u'Directory {0} updated'.format(name)
if ret['changes']:
ret['comment'] = u'Directory {0} updated'.format(name)
if __opts__['test']:
ret['comment'] = 'Directory {0} not updated'.format(name)

View File

@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Domains
========================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create domain:
keystone_domain.present:
- name: domain1
create domain with optional params:
keystone_domain.present:
- name: domain1
- enabled: False
- description: 'my domain'
delete domain:
keystone_domain.absent:
- name: domain1
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_domain'
def __virtual__():
if 'keystoneng.domain_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def present(name, auth=None, **kwargs):
'''
Ensure domain exists and is up-to-date
name
Name of the domain
enabled
Boolean to control if domain is enabled
description
An arbitrary description of the domain
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
domain = __salt__['keystoneng.domain_get'](name=name)
if not domain:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Domain {} will be created.'.format(name)
return ret
kwargs['name'] = name
domain = __salt__['keystoneng.domain_create'](**kwargs)
ret['changes'] = domain
ret['comment'] = 'Created domain'
return ret
changes = __salt__['keystoneng.compare_changes'](domain, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'Domain {} will be updated.'.format(name)
return ret
kwargs['domain_id'] = domain.id
__salt__['keystoneng.domain_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated domain'
return ret
def absent(name, auth=None):
'''
Ensure domain does not exist
name
Name of the domain
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
domain = __salt__['keystoneng.domain_get'](name=name)
if domain:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'name': name}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Domain {} will be deleted.'.format(name)
return ret
__salt__['keystoneng.domain_delete'](name=domain)
ret['changes']['id'] = domain.id
ret['comment'] = 'Deleted domain'
return ret

View File

@ -0,0 +1,185 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Endpoints
==========================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create endpoint:
keystone_endpoint.present:
- name: public
- url: https://example.org:9292
- region: RegionOne
- service_name: glance
destroy endpoint:
keystone_endpoint.absent:
- name: public
- url: https://example.org:9292
- region: RegionOne
- service_name: glance
create multiple endpoints:
keystone_endpoint.absent:
- names:
- public
- admin
- internal
- url: https://example.org:9292
- region: RegionOne
- service_name: glance
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_endpoint'
def __virtual__():
if 'keystoneng.endpoint_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def _common(ret, name, service_name, kwargs):
'''
Returns: tuple whose first element is a bool indicating success or failure
and the second element is either a ret dict for salt or an object
'''
if 'interface' not in kwargs and 'public_url' not in kwargs:
kwargs['interface'] = name
service = __salt__['keystoneng.service_get'](name_or_id=service_name)
if not service:
ret['comment'] = 'Cannot find service'
ret['result'] = False
return (False, ret)
filters = kwargs.copy()
filters.pop('enabled', None)
filters.pop('url', None)
filters['service_id'] = service.id
kwargs['service_name_or_id'] = service.id
endpoints = __salt__['keystoneng.endpoint_search'](filters=filters)
if len(endpoints) > 1:
ret['comment'] = "Multiple endpoints match criteria"
ret['result'] = False
return ret
endpoint = endpoints[0] if endpoints else None
return (True, endpoint)
def present(name, service_name, auth=None, **kwargs):
'''
Ensure an endpoint exists and is up-to-date
name
Interface name
url
URL of the endpoint
service_name
Service name or ID
region
The region name to assign the endpoint
enabled
Boolean to control if endpoint is enabled
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
success, val = _, endpoint = _common(ret, name, service_name, kwargs)
if not success:
return val
if not endpoint:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Endpoint will be created.'
return ret
# NOTE(SamYaple): Endpoints are returned as a list which can contain
# several items depending on the options passed
endpoints = __salt__['keystoneng.endpoint_create'](**kwargs)
if len(endpoints) == 1:
ret['changes'] = endpoints[0]
else:
for i, endpoint in enumerate(endpoints):
ret['changes'][i] = endpoint
ret['comment'] = 'Created endpoint'
return ret
changes = __salt__['keystoneng.compare_changes'](endpoint, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'Endpoint will be updated.'
return ret
kwargs['endpoint_id'] = endpoint.id
__salt__['keystoneng.endpoint_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated endpoint'
return ret
def absent(name, service_name, auth=None, **kwargs):
'''
Ensure an endpoint does not exists
name
Interface name
url
URL of the endpoint
service_name
Service name or ID
region
The region name to assign the endpoint
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
success, val = _, endpoint = _common(ret, name, service_name, kwargs)
if not success:
return val
if endpoint:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': endpoint.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Endpoint will be deleted.'
return ret
__salt__['keystoneng.endpoint_delete'](id=endpoint.id)
ret['changes']['id'] = endpoint.id
ret['comment'] = 'Deleted endpoint'
return ret

View File

@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Groups
=======================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create group:
keystone_group.present:
- name: group1
delete group:
keystone_group.absent:
- name: group1
create group with optional params:
keystone_group.present:
- name: group1
- domain: domain1
- description: 'my group'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_endpoint'
def __virtual__():
if 'keystoneng.group_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def _common(kwargs):
'''
Returns: None if group wasn't found, otherwise a group object
'''
search_kwargs = {'name': kwargs['name']}
if 'domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('domain'))
domain_id = domain.id if hasattr(domain, 'id') else domain
search_kwargs['filters'] = {'domain_id': domain_id}
kwargs['domain'] = domain
return __salt__['keystoneng.group_get'](**search_kwargs)
def present(name, auth=None, **kwargs):
'''
Ensure an group exists and is up-to-date
name
Name of the group
domain
The name or id of the domain
description
An arbitrary description of the group
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_cloud'](auth)
kwargs['name'] = name
group = _common(kwargs)
if group is None:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Group will be created.'
return ret
group = __salt__['keystoneng.group_create'](**kwargs)
ret['changes'] = group
ret['comment'] = 'Created group'
return ret
changes = __salt__['keystoneng.compare_changes'](group, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'Group will be updated.'
return ret
__salt__['keystoneng.group_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated group'
return ret
def absent(name, auth=None, **kwargs):
'''
Ensure group does not exist
name
Name of the group
domain
The name or id of the domain
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_cloud'](auth)
kwargs['name'] = name
group = _common(kwargs)
if group:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': group.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Group will be deleted.'
return ret
__salt__['keystoneng.group_delete'](name=group)
ret['changes']['id'] = group.id
ret['comment'] = 'Deleted group'
return ret

View File

@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Projects
=========================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create project:
keystone_project.present:
- name: project1
delete project:
keystone_project.absent:
- name: project1
create project with optional params:
keystone_project.present:
- name: project1
- domain: domain1
- enabled: False
- description: 'my project'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_project'
def __virtual__():
if 'keystoneng.project_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def _common(name, kwargs):
'''
Returns: None if project wasn't found, otherwise a group object
'''
search_kwargs = {'name': name}
if 'domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('domain'))
domain_id = domain.id if hasattr(domain, 'id') else domain
search_kwargs['domain_id'] = domain_id
kwargs['domain_id'] = domain_id
return __salt__['keystoneng.project_get'](**search_kwargs)
def present(name, auth=None, **kwargs):
'''
Ensure a project exists and is up-to-date
name
Name of the project
domain
The name or id of the domain
description
An arbitrary description of the project
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
project = _common(name, kwargs)
if project is None:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Project will be created.'
return ret
project = __salt__['keystoneng.project_create'](**kwargs)
ret['changes'] = project
ret['comment'] = 'Created project'
return ret
changes = __salt__['keystoneng.compare_changes'](project, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'Project will be updated.'
return ret
__salt__['keystoneng.project_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated project'
return ret
def absent(name, auth=None, **kwargs):
'''
Ensure a project does not exists
name
Name of the project
domain
The name or id of the domain
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
project = _common(name, kwargs)
if project:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': project.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Project will be deleted.'
return ret
__salt__['keystoneng.project_delete'](name=project)
ret['changes']['id'] = project.id
ret['comment'] = 'Deleted project'
return ret

View File

@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Roles
======================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create role:
keystone_role.present:
- name: role1
delete role:
keystone_role.absent:
- name: role1
create role with optional params:
keystone_role.present:
- name: role1
- description: 'my group'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_role'
def __virtual__():
if 'keystoneng.role_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def present(name, auth=None, **kwargs):
'''
Ensure an role exists
name
Name of the role
description
An arbitrary description of the role
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
role = __salt__['keystoneng.role_get'](**kwargs)
if not role:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Role will be created.'
return ret
role = __salt__['keystoneng.role_create'](**kwargs)
ret['changes']['id'] = role.id
ret['changes']['name'] = role.name
ret['comment'] = 'Created role'
return ret
# NOTE(SamYaple): Update support pending https://review.openstack.org/#/c/496992/
return ret
def absent(name, auth=None, **kwargs):
'''
Ensure role does not exist
name
Name of the role
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
role = __salt__['keystoneng.role_get'](**kwargs)
if role:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': role.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Role will be deleted.'
return ret
__salt__['keystoneng.role_delete'](name=role)
ret['changes']['id'] = role.id
ret['comment'] = 'Deleted role'
return ret

View File

@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Role Grants
============================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create group:
keystone_group.present:
- name: group1
delete group:
keystone_group.absent:
- name: group1
create group with optional params:
keystone_group.present:
- name: group1
- domain: domain1
- description: 'my group'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_role_grant'
def __virtual__():
if 'keystoneng.role_grant' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def _get_filters(kwargs):
role_kwargs = {'name': kwargs.pop('role')}
if 'role_domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('role_domain'))
if domain:
role_kwargs['domain_id'] = domain.id \
if hasattr(domain, 'id') else domain
role = __salt__['keystoneng.role_get'](**role_kwargs)
kwargs['name'] = role
filters = {'role': role.id if hasattr(role, 'id') else role}
if 'domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('domain'))
kwargs['domain'] = filters['domain'] = \
domain.id if hasattr(domain, 'id') else domain
if 'project' in kwargs:
project_kwargs = {'name': kwargs.pop('project')}
if 'project_domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('project_domain'))
if domain:
project_kwargs['domain_id'] = domain.id
project = __salt__['keystoneng.get_entity'](
'project', **project_kwargs)
kwargs['project'] = project
filters['project'] = project.id if hasattr(project, 'id') else project
if 'user' in kwargs:
user_kwargs = {'name': kwargs.pop('user')}
if 'user_domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('user_domain'))
if domain:
user_kwargs['domain_id'] = domain.id
user = __salt__['keystoneng.get_entity']('user', **user_kwargs)
kwargs['user'] = user
filters['user'] = user.id if hasattr(user, 'id') else user
if 'group' in kwargs:
group_kwargs = {'name': kwargs['group']}
if 'group_domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('group_domain'))
if domain:
group_kwargs['domain_id'] = domain.id
group = __salt__['keystoneng.get_entity']('group', **group_kwargs)
kwargs['group'] = group
filters['group'] = group.id if hasattr(group, 'id') else group
return filters, kwargs
def present(name, auth=None, **kwargs):
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
if 'role' not in kwargs:
kwargs['role'] = name
filters, kwargs = _get_filters(kwargs)
grants = __salt__['keystoneng.role_assignment_list'](filters=filters)
if not grants:
__salt__['keystoneng.role_grant'](**kwargs)
for k, v in filters.items():
ret['changes'][k] = v
ret['comment'] = 'Granted role assignment'
return ret
def absent(name, auth=None, **kwargs):
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
if 'role' not in kwargs:
kwargs['role'] = name
filters, kwargs = _get_filters(kwargs)
grants = __salt__['keystoneng.role_assignment_list'](filters=filters)
if grants:
__salt__['keystoneng.role_revoke'](**kwargs)
for k, v in filters.items():
ret['changes'][k] = v
ret['comment'] = 'Revoked role assignment'
return ret

View File

@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Services
=========================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create service:
keystone_service.present:
- name: glance
- type: image
delete service:
keystone_service.absent:
- name: glance
create service with optional params:
keystone_service.present:
- name: glance
- type: image
- enabled: False
- description: 'OpenStack Image'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_service'
def __virtual__():
if 'keystoneng.service_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def present(name, auth=None, **kwargs):
'''
Ensure an service exists and is up-to-date
name
Name of the group
type
Service type
enabled
Boolean to control if service is enabled
description
An arbitrary description of the service
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
service = __salt__['keystoneng.service_get'](name=name)
if service is None:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'Service will be created.'
return ret
kwargs['name'] = name
service = __salt__['keystoneng.service_create'](**kwargs)
ret['changes'] = service
ret['comment'] = 'Created service'
return ret
changes = __salt__['keystoneng.compare_changes'](service, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'Service will be updated.'
return ret
kwargs['name'] = service
__salt__['keystoneng.service_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated service'
return ret
def absent(name, auth=None):
'''
Ensure service does not exist
name
Name of the service
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
service = __salt__['keystoneng.service_get'](name=name)
if service:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': service.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'Service will be deleted.'
return ret
__salt__['keystoneng.service_delete'](name=service)
ret['changes']['id'] = service.id
ret['comment'] = 'Deleted service'
return ret

View File

@ -0,0 +1,153 @@
# -*- coding: utf-8 -*-
'''
Management of OpenStack Keystone Users
======================================
.. versionadded:: Nitrogen
:depends: shade
:configuration: see :py:mod:`salt.modules.keystoneng` for setup instructions
Example States
.. code-block:: yaml
create user:
keystone_user.present:
- name: user1
delete user:
keystone_user.absent:
- name: user1
create user with optional params:
keystone_user.present:
- name: user1
- domain: domain1
- enabled: False
- password: password123
- email: "user1@example.org"
- description: 'my user'
'''
from __future__ import absolute_import
__virtualname__ = 'keystone_user'
def __virtual__():
if 'keystoneng.user_get' in __salt__:
return __virtualname__
return (False, 'The keystoneng execution module failed to load: shade python module is not available')
def _common(kwargs):
'''
Returns: None if user wasn't found, otherwise a user object
'''
search_kwargs = {'name': kwargs['name']}
if 'domain' in kwargs:
domain = __salt__['keystoneng.get_entity'](
'domain', name=kwargs.pop('domain'))
domain_id = domain.id if hasattr(domain, 'id') else domain
search_kwargs['domain_id'] = domain_id
kwargs['domain_id'] = domain_id
return __salt__['keystoneng.user_get'](**search_kwargs)
def present(name, auth=None, **kwargs):
'''
Ensure domain exists and is up-to-date
name
Name of the domain
domain
The name or id of the domain
enabled
Boolean to control if domain is enabled
description
An arbitrary description of the domain
password
The user password
email
The users email address
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
user = _common(kwargs)
if user is None:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = kwargs
ret['pchanges'] = ret['changes']
ret['comment'] = 'User will be created.'
return ret
user = __salt__['keystoneng.user_create'](**kwargs)
ret['changes'] = user
ret['comment'] = 'Created user'
return ret
changes = __salt__['keystoneng.compare_changes'](user, **kwargs)
if changes:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = changes
ret['pchanges'] = ret['changes']
ret['comment'] = 'User will be updated.'
return ret
kwargs['name'] = user
__salt__['keystoneng.user_update'](**kwargs)
ret['changes'].update(changes)
ret['comment'] = 'Updated user'
return ret
def absent(name, auth=None, **kwargs):
'''
Ensure user does not exists
name
Name of the user
domain
The name or id of the domain
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
__salt__['keystoneng.setup_clouds'](auth)
kwargs['name'] = name
user = _common(kwargs)
if user:
if __opts__['test'] is True:
ret['result'] = None
ret['changes'] = {'id': user.id}
ret['pchanges'] = ret['changes']
ret['comment'] = 'User will be deleted.'
return ret
__salt__['keystoneng.user_delete'](name=user)
ret['changes']['id'] = user.id
ret['comment'] = 'Deleted user'
return ret

View File

@ -508,8 +508,10 @@ def _find_install_targets(name=None,
# 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': {},
@ -669,6 +671,9 @@ def _find_install_targets(name=None,
failed_verify = False
for key, val in six.iteritems(desired):
cver = cur_pkgs.get(key, [])
if resolve_capabilities and not cver and key in cur_prov:
cver = cur_pkgs.get(cur_prov.get(key)[0], [])
# Package not yet installed, so add to targets
if not cver:
targets[key] = val
@ -786,13 +791,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
@ -809,6 +816,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:
failed.append(pkgname)
@ -873,6 +882,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,
@ -1105,6 +1134,11 @@ def installed(
.. versionadded:: 2014.1.1
:param bool resolve_capabilities:
Turn on resolving capabilities. This allow to name "provides" or alias names for packages.
.. versionadded:: Oxygen
: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
@ -1448,6 +1482,12 @@ def installed(
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)) \
@ -1707,8 +1747,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
@ -1927,6 +1972,11 @@ def downloaded(name,
- dos2unix
- salt-minion: 2015.8.5-1.el6
:param bool resolve_capabilities:
Turn on resolving capabilities. This allow to name "provides" or alias names for packages.
.. versionadded:: Oxygen
CLI Example:
.. code-block:: yaml
@ -1952,11 +2002,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.
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,
@ -2203,6 +2264,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 to name "provides" or alias names for packages.
.. versionadded:: Oxygen
Multiple Package Installation Options:
@ -2300,6 +2365,10 @@ def latest(
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,
@ -2822,6 +2891,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 to name "provides" or alias names for packages.
.. versionadded:: Oxygen
kwargs
Any keyword arguments to pass through to ``pkg.upgrade``.
@ -2842,6 +2916,7 @@ 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)
if isinstance(pkgs, list):

View File

@ -451,10 +451,10 @@ def format_call(fun,
continue
extra[key] = copy.deepcopy(value)
# We'll be showing errors to the users until Salt Oxygen comes out, after
# We'll be showing errors to the users until Salt Fluorine comes out, after
# which, errors will be raised instead.
salt.utils.versions.warn_until(
'Oxygen',
'Fluorine',
'It\'s time to start raising `SaltInvocationError` instead of '
'returning warnings',
# Let's not show the deprecation warning on the console, there's no
@ -491,7 +491,7 @@ def format_call(fun,
'{0}. If you were trying to pass additional data to be used '
'in a template context, please populate \'context\' with '
'\'key: value\' pairs. Your approach will work until Salt '
'Oxygen is out.{1}'.format(
'Fluorine is out.{1}'.format(
msg,
'' if 'full' not in ret else ' Please update your state files.'
)

View File

@ -89,6 +89,28 @@ localtime.
This will schedule the command: ``state.sls httpd test=True`` at 5:00 PM on
Monday, Wednesday and Friday, and 3:00 PM on Tuesday and Thursday.
.. code-block:: yaml
schedule:
job1:
function: state.sls
args:
- httpd
kwargs:
test: True
when:
- 'tea time'
.. code-block:: yaml
whens:
tea time: 1:40pm
deployment time: Friday 5:00pm
The Salt scheduler also allows custom phrases to be used for the `when`
parameter. These `whens` can be stored as either pillar values or
grain values.
.. code-block:: yaml
schedule:
@ -333,7 +355,6 @@ import logging
import errno
import random
import yaml
import copy
# Import Salt libs
import salt.config
@ -409,6 +430,7 @@ class Schedule(object):
self.proxy = proxy
self.functions = functions
self.standalone = standalone
self.skip_function = None
if isinstance(intervals, dict):
self.intervals = intervals
else:
@ -745,6 +767,69 @@ class Schedule(object):
evt.fire_event({'complete': True},
tag='/salt/minion/minion_schedule_saved')
def postpone_job(self, name, data):
'''
Postpone a job in the scheduler.
Ignores jobs from pillar
'''
time = data['time']
new_time = data['new_time']
# ensure job exists, then disable it
if name in self.opts['schedule']:
if 'skip_explicit' not in self.opts['schedule'][name]:
self.opts['schedule'][name]['skip_explicit'] = []
self.opts['schedule'][name]['skip_explicit'].append(time)
if 'run_explicit' not in self.opts['schedule'][name]:
self.opts['schedule'][name]['run_explicit'] = []
self.opts['schedule'][name]['run_explicit'].append(new_time)
elif name in self._get_schedule(include_opts=False):
log.warning('Cannot modify job {0}, '
'it`s in the pillar!'.format(name))
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'schedule': self._get_schedule()},
tag='/salt/minion/minion_schedule_postpone_job_complete')
def skip_job(self, name, data):
'''
Skip a job at a specific time in the scheduler.
Ignores jobs from pillar
'''
time = data['time']
# ensure job exists, then disable it
if name in self.opts['schedule']:
if 'skip_explicit' not in self.opts['schedule'][name]:
self.opts['schedule'][name]['skip_explicit'] = []
self.opts['schedule'][name]['skip_explicit'].append(time)
elif name in self._get_schedule(include_opts=False):
log.warning('Cannot modify job {0}, '
'it`s in the pillar!'.format(name))
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'schedule': self._get_schedule()},
tag='/salt/minion/minion_schedule_skip_job_complete')
def get_next_fire_time(self, name):
'''
Disable a job in the scheduler. Ignores jobs from pillar
'''
schedule = self._get_schedule()
if schedule:
_next_fire_time = schedule[name]['_next_fire_time']
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'next_fire_time': _next_fire_time},
tag='/salt/minion/minion_schedule_next_fire_time_complete')
def handle_func(self, multiprocessing_enabled, func, data):
'''
Execute this method in a multiprocess or thread
@ -948,11 +1033,16 @@ class Schedule(object):
# Let's make sure we exit the process!
sys.exit(salt.defaults.exitcodes.EX_GENERIC)
def eval(self):
def eval(self, now=None):
'''
Evaluate and execute the schedule
:param int now: Override current time with a Unix timestamp``
'''
log.trace('==== evaluating schedule =====')
def _splay(splaytime):
'''
Calculate splaytime
@ -974,9 +1064,13 @@ class Schedule(object):
raise ValueError('Schedule must be of type dict.')
if 'enabled' in schedule and not schedule['enabled']:
return
if 'skip_function' in schedule:
self.skip_function = schedule['skip_function']
for job, data in six.iteritems(schedule):
if job == 'enabled' or not data:
continue
if job == 'skip_function' or not data:
continue
if not isinstance(data, dict):
log.error('Scheduled job "{0}" should have a dict value, not {1}'.format(job, type(data)))
continue
@ -1011,7 +1105,8 @@ class Schedule(object):
'_run_on_start' not in data:
data['_run_on_start'] = True
now = int(time.time())
if not now:
now = int(time.time())
if 'until' in data:
if not _WHEN_SUPPORTED:
@ -1065,6 +1160,23 @@ class Schedule(object):
'", "'.join(scheduling_elements)))
continue
if 'run_explicit' in data:
_run_explicit = data['run_explicit']
if isinstance(_run_explicit, six.string_types):
_run_explicit = [_run_explicit]
# Copy the list so we can loop through it
for i in copy.deepcopy(_run_explicit):
if len(_run_explicit) > 1:
if i < now - self.opts['loop_interval']:
_run_explicit.remove(i)
if _run_explicit:
if _run_explicit[0] <= now < (_run_explicit[0] + self.opts['loop_interval']):
run = True
data['_next_fire_time'] = _run_explicit[0]
if True in [True for item in time_elements if item in data]:
if '_seconds' not in data:
interval = int(data.get('seconds', 0))
@ -1153,10 +1265,11 @@ class Schedule(object):
# Copy the list so we can loop through it
for i in copy.deepcopy(_when):
if i < now and len(_when) > 1:
# Remove all missed schedules except the latest one.
# We need it to detect if it was triggered previously.
_when.remove(i)
if len(_when) > 1:
if i < now - self.opts['loop_interval']:
# Remove all missed schedules except the latest one.
# We need it to detect if it was triggered previously.
_when.remove(i)
if _when:
# Grab the first element, which is the next run time or
@ -1258,19 +1371,21 @@ class Schedule(object):
seconds = data['_next_fire_time'] - now
if data['_splay']:
seconds = data['_splay'] - now
if seconds <= 0:
if '_seconds' in data:
if '_seconds' in data:
if seconds <= 0:
run = True
elif 'when' in data and data['_run']:
elif 'when' in data and data['_run']:
if data['_next_fire_time'] <= now <= (data['_next_fire_time'] + self.opts['loop_interval']):
data['_run'] = False
run = True
elif 'cron' in data:
# Reset next scheduled time because it is in the past now,
# and we should trigger the job run, then wait for the next one.
elif 'cron' in data:
# Reset next scheduled time because it is in the past now,
# and we should trigger the job run, then wait for the next one.
if seconds <= 0:
data['_next_fire_time'] = None
run = True
elif seconds == 0:
run = True
elif seconds == 0:
run = True
if '_run_on_start' in data and data['_run_on_start']:
run = True
@ -1312,7 +1427,11 @@ class Schedule(object):
if start <= now <= end:
run = True
else:
run = False
if self.skip_function:
run = True
func = self.skip_function
else:
run = False
else:
log.error('schedule.handle_func: Invalid range, end must be larger than start. \
Ignoring job {0}.'.format(job))
@ -1322,6 +1441,62 @@ class Schedule(object):
Ignoring job {0}.'.format(job))
continue
if 'skip_during_range' in data:
if not _RANGE_SUPPORTED:
log.error('Missing python-dateutil. Ignoring job {0}'.format(job))
continue
else:
if isinstance(data['skip_during_range'], dict):
try:
start = int(time.mktime(dateutil_parser.parse(data['skip_during_range']['start']).timetuple()))
except ValueError:
log.error('Invalid date string for start in skip_during_range. Ignoring job {0}.'.format(job))
continue
try:
end = int(time.mktime(dateutil_parser.parse(data['skip_during_range']['end']).timetuple()))
except ValueError:
log.error('Invalid date string for end in skip_during_range. Ignoring job {0}.'.format(job))
log.error(data)
continue
if end > start:
if start <= now <= end:
if self.skip_function:
run = True
func = self.skip_function
else:
run = False
else:
run = True
else:
log.error('schedule.handle_func: Invalid range, end must be larger than start. \
Ignoring job {0}.'.format(job))
continue
else:
log.error('schedule.handle_func: Invalid, range must be specified as a dictionary. \
Ignoring job {0}.'.format(job))
continue
if 'skip_explicit' in data:
_skip_explicit = data['skip_explicit']
if isinstance(_skip_explicit, six.string_types):
_skip_explicit = [_skip_explicit]
# Copy the list so we can loop through it
for i in copy.deepcopy(_skip_explicit):
if i < now - self.opts['loop_interval']:
_skip_explicit.remove(i)
if _skip_explicit:
if _skip_explicit[0] <= now <= (_skip_explicit[0] + self.opts['loop_interval']):
if self.skip_function:
run = True
func = self.skip_function
else:
run = False
else:
run = True
if not run:
continue
@ -1374,6 +1549,7 @@ class Schedule(object):
finally:
if '_seconds' in data:
data['_next_fire_time'] = now + data['_seconds']
data['_last_run'] = now
data['_splay'] = None
if salt.utils.platform.is_windows():
# Restore our function references.

View File

@ -93,7 +93,8 @@ class SaltUtilSyncModuleTest(ModuleCase):
'states': [],
'sdb': [],
'proxymodules': [],
'output': []}
'output': [],
'thorium': []}
ret = self.run_function('saltutil.sync_all')
self.assertEqual(ret, expected_return)
@ -113,7 +114,8 @@ class SaltUtilSyncModuleTest(ModuleCase):
'states': [],
'sdb': [],
'proxymodules': [],
'output': []}
'output': [],
'thorium': []}
ret = self.run_function('saltutil.sync_all', extmod_whitelist={'modules': ['salttest']})
self.assertEqual(ret, expected_return)
@ -135,7 +137,8 @@ class SaltUtilSyncModuleTest(ModuleCase):
'states': [],
'sdb': [],
'proxymodules': [],
'output': []}
'output': [],
'thorium': []}
ret = self.run_function('saltutil.sync_all', extmod_blacklist={'modules': ['runtests_decorators']})
self.assertEqual(ret, expected_return)
@ -155,7 +158,8 @@ class SaltUtilSyncModuleTest(ModuleCase):
'states': [],
'sdb': [],
'proxymodules': [],
'output': []}
'output': [],
'thorium': []}
ret = self.run_function('saltutil.sync_all', extmod_whitelist={'modules': ['runtests_decorators']},
extmod_blacklist={'modules': ['runtests_decorators']})
self.assertEqual(ret, expected_return)

View File

@ -37,11 +37,15 @@ _PKG_TARGETS = {
'Debian': ['python-plist', 'apg'],
'RedHat': ['units', 'zsh-html'],
'FreeBSD': ['aalib', 'pth'],
'Suse': ['aalib', 'python-pssh'],
'Suse': ['aalib', 'rpm-python'],
'MacOS': ['libpng', 'jpeg'],
'Windows': ['firefox', '7zip'],
}
_PKG_CAP_TARGETS = {
'Suse': [('w3m_ssl', 'w3m')],
}
_PKG_TARGETS_32 = {
'CentOS': 'xz-devel.i686'
}
@ -793,3 +797,260 @@ class PkgTest(ModuleCase, SaltReturnAssertsMixin):
self.assertEqual(ret_comment, 'An error was encountered while installing/updating group '
'\'handle_missing_pkg_group\': Group \'handle_missing_pkg_group\' '
'not found.')
@skipIf(salt.utils.platform.is_windows(), 'minion is windows')
@requires_system_grains
def test_pkg_cap_001_installed(self, grains=None):
'''
This is a destructive test as it installs and then removes a package
'''
# Skip test if package manager not available
if not pkgmgr_avail(self.run_function, self.run_function('grains.items')):
self.skipTest('Package manager is not available')
os_family = grains.get('os_family', '')
pkg_cap_targets = _PKG_CAP_TARGETS.get(os_family, [])
if not len(pkg_cap_targets) > 0:
self.skipTest('Capability not provided')
target, realpkg = pkg_cap_targets[0]
version = self.run_function('pkg.version', [target])
realver = self.run_function('pkg.version', [realpkg])
# If this assert fails, we need to find new targets, this test needs to
# be able to test successful installation of packages, so this package
# needs to not be installed before we run the states below
self.assertFalse(version)
self.assertFalse(realver)
ret = self.run_state('pkg.installed', name=target, refresh=False, resolve_capabilities=True, test=True)
self.assertInSaltComment("The following packages would be installed/updated: {0}".format(realpkg), ret)
ret = self.run_state('pkg.installed', name=target, refresh=False, resolve_capabilities=True)
self.assertSaltTrueReturn(ret)
ret = self.run_state('pkg.removed', name=realpkg)
self.assertSaltTrueReturn(ret)
@skipIf(salt.utils.platform.is_windows(), 'minion is windows')
@requires_system_grains
def test_pkg_cap_002_already_installed(self, grains=None):
'''
This is a destructive test as it installs and then removes a package
'''
# Skip test if package manager not available
if not pkgmgr_avail(self.run_function, self.run_function('grains.items')):
self.skipTest('Package manager is not available')
os_family = grains.get('os_family', '')
pkg_cap_targets = _PKG_CAP_TARGETS.get(os_family, [])
if not len(pkg_cap_targets) > 0:
self.skipTest('Capability not provided')
target, realpkg = pkg_cap_targets[0]
version = self.run_function('pkg.version', [target])
realver = self.run_function('pkg.version', [realpkg])
# If this assert fails, we need to find new targets, this test needs to
# be able to test successful installation of packages, so this package
# needs to not be installed before we run the states below
self.assertFalse(version)
self.assertFalse(realver)
# install the package already
ret = self.run_state('pkg.installed', name=realpkg, refresh=False)
ret = self.run_state('pkg.installed', name=target, refresh=False, resolve_capabilities=True, test=True)
self.assertInSaltComment("All specified packages are already installed", ret)
ret = self.run_state('pkg.installed', name=target, refresh=False, resolve_capabilities=True)
self.assertSaltTrueReturn(ret)
self.assertInSaltComment("packages are already installed", ret)
ret = self.run_state('pkg.removed', name=realpkg)
self.assertSaltTrueReturn(ret)
@skipIf(salt.utils.platform.is_windows(), 'minion is windows')
@requires_system_grains
def test_pkg_cap_003_installed_multipkg_with_version(self, grains=None):
'''
This is a destructive test as it installs and then removes two packages
'''
# Skip test if package manager not available
if not pkgmgr_avail(self.run_function, self.run_function('grains.items')):
self.skipTest('Package manager is not available')
os_family = grains.get('os_family', '')
pkg_cap_targets = _PKG_CAP_TARGETS.get(os_family, [])
if not len(pkg_cap_targets) > 0:
self.skipTest('Capability not provided')
pkg_targets = _PKG_TARGETS.get(os_family, [])
# Don't perform this test on FreeBSD since version specification is not
# supported.
if os_family == 'FreeBSD':
return
# Make sure that we have targets that match the os_family. If this
# fails then the _PKG_TARGETS dict above needs to have an entry added,
# with two packages that are not installed before these tests are run
self.assertTrue(bool(pkg_cap_targets))
self.assertTrue(bool(pkg_targets))
if os_family == 'Arch':
for idx in range(13):
if idx == 12:
raise Exception('Package database locked after 60 seconds, '
'bailing out')
if not os.path.isfile('/var/lib/pacman/db.lck'):
break
time.sleep(5)
capability, realpkg = pkg_cap_targets[0]
version = latest_version(self.run_function, pkg_targets[0])
realver = latest_version(self.run_function, realpkg)
# If this assert fails, we need to find new targets, this test needs to
# be able to test successful installation of packages, so these
# packages need to not be installed before we run the states below
self.assertTrue(bool(version))
self.assertTrue(bool(realver))
pkgs = [{pkg_targets[0]: version}, pkg_targets[1], {capability: realver}]
ret = self.run_state('pkg.installed',
name='test_pkg_cap_003_installed_multipkg_with_version-install',
pkgs=pkgs,
refresh=False)
self.assertSaltFalseReturn(ret)
ret = self.run_state('pkg.installed',
name='test_pkg_cap_003_installed_multipkg_with_version-install-capability',
pkgs=pkgs,
refresh=False, resolve_capabilities=True, test=True)
self.assertInSaltComment("packages would be installed/updated", ret)
self.assertInSaltComment("{0}={1}".format(realpkg, realver), ret)
ret = self.run_state('pkg.installed',
name='test_pkg_cap_003_installed_multipkg_with_version-install-capability',
pkgs=pkgs,
refresh=False, resolve_capabilities=True)
self.assertSaltTrueReturn(ret)
cleanup_pkgs = pkg_targets
cleanup_pkgs.append(realpkg)
ret = self.run_state('pkg.removed',
name='test_pkg_cap_003_installed_multipkg_with_version-remove',
pkgs=cleanup_pkgs)
self.assertSaltTrueReturn(ret)
@skipIf(salt.utils.platform.is_windows(), 'minion is windows')
@requires_system_grains
def test_pkg_cap_004_latest(self, grains=None):
'''
This tests pkg.latest with a package that has no epoch (or a zero
epoch).
'''
# Skip test if package manager not available
if not pkgmgr_avail(self.run_function, self.run_function('grains.items')):
self.skipTest('Package manager is not available')
os_family = grains.get('os_family', '')
pkg_cap_targets = _PKG_CAP_TARGETS.get(os_family, [])
if not len(pkg_cap_targets) > 0:
self.skipTest('Capability not provided')
target, realpkg = pkg_cap_targets[0]
version = self.run_function('pkg.version', [target])
realver = self.run_function('pkg.version', [realpkg])
# If this assert fails, we need to find new targets, this test needs to
# be able to test successful installation of packages, so this package
# needs to not be installed before we run the states below
self.assertFalse(version)
self.assertFalse(realver)
ret = self.run_state('pkg.latest', name=target, refresh=False, resolve_capabilities=True, test=True)
self.assertInSaltComment("The following packages would be installed/upgraded: {0}".format(realpkg), ret)
ret = self.run_state('pkg.latest', name=target, refresh=False, resolve_capabilities=True)
self.assertSaltTrueReturn(ret)
ret = self.run_state('pkg.latest', name=target, refresh=False, resolve_capabilities=True)
self.assertSaltTrueReturn(ret)
self.assertInSaltComment("is already up-to-date", ret)
ret = self.run_state('pkg.removed', name=realpkg)
self.assertSaltTrueReturn(ret)
@skipIf(salt.utils.platform.is_windows(), 'minion is windows')
@requires_system_grains
def test_pkg_cap_005_downloaded(self, grains=None):
'''
This is a destructive test as it installs and then removes a package
'''
# Skip test if package manager not available
if not pkgmgr_avail(self.run_function, self.run_function('grains.items')):
self.skipTest('Package manager is not available')
os_family = grains.get('os_family', '')
pkg_cap_targets = _PKG_CAP_TARGETS.get(os_family, [])
if not len(pkg_cap_targets) > 0:
self.skipTest('Capability not provided')
target, realpkg = pkg_cap_targets[0]
version = self.run_function('pkg.version', [target])
realver = self.run_function('pkg.version', [realpkg])
# If this assert fails, we need to find new targets, this test needs to
# be able to test successful installation of packages, so this package
# needs to not be installed before we run the states below
self.assertFalse(version)
self.assertFalse(realver)
ret = self.run_state('pkg.downloaded', name=target, refresh=False)
self.assertSaltFalseReturn(ret)
ret = self.run_state('pkg.downloaded', name=target, refresh=False, resolve_capabilities=True, test=True)
self.assertInSaltComment("The following packages would be downloaded: {0}".format(realpkg), ret)
ret = self.run_state('pkg.downloaded', name=target, refresh=False, resolve_capabilities=True)
self.assertSaltTrueReturn(ret)
@skipIf(salt.utils.platform.is_windows(), 'minion is windows')
@requires_system_grains
def test_pkg_cap_006_uptodate(self, grains=None):
'''
This is a destructive test as it installs and then removes a package
'''
# Skip test if package manager not available
if not pkgmgr_avail(self.run_function, self.run_function('grains.items')):
self.skipTest('Package manager is not available')
os_family = grains.get('os_family', '')
pkg_cap_targets = _PKG_CAP_TARGETS.get(os_family, [])
if not len(pkg_cap_targets) > 0:
self.skipTest('Capability not provided')
target, realpkg = pkg_cap_targets[0]
version = self.run_function('pkg.version', [target])
realver = self.run_function('pkg.version', [realpkg])
# If this assert fails, we need to find new targets, this test needs to
# be able to test successful installation of packages, so this package
# needs to not be installed before we run the states below
self.assertFalse(version)
self.assertFalse(realver)
ret = self.run_state('pkg.installed', name=target,
refresh=False, resolve_capabilities=True)
self.assertSaltTrueReturn(ret)
ret = self.run_state('pkg.uptodate',
name='test_pkg_cap_006_uptodate',
pkgs=[target],
refresh=False,
resolve_capabilities=True)
self.assertSaltTrueReturn(ret)
self.assertInSaltComment("System is already up-to-date", ret)
ret = self.run_state('pkg.removed', name=realpkg)
self.assertSaltTrueReturn(ret)
ret = self.run_state('pkg.uptodate',
name='test_pkg_cap_006_uptodate',
refresh=False,
test=True)
self.assertInSaltComment("System update will be performed", ret)

View File

@ -278,6 +278,38 @@ class LazyLoaderWhitelistTest(TestCase):
self.assertNotIn('grains.get', self.loader)
class LazyLoaderSingleItem(TestCase):
'''
Test loading a single item via the _load() function
'''
@classmethod
def setUpClass(cls):
cls.opts = salt.config.minion_config(None)
cls.opts['grains'] = grains(cls.opts)
def setUp(self):
self.loader = LazyLoader(_module_dirs(copy.deepcopy(self.opts), 'modules', 'module'),
copy.deepcopy(self.opts),
tag='module')
def tearDown(self):
del self.loader
def test_single_item_no_dot(self):
'''
Checks that a KeyError is raised when the function key does not contain a '.'
'''
with self.assertRaises(KeyError) as err:
inspect.isfunction(self.loader['testing_no_dot'])
if six.PY2:
self.assertEqual(err.exception[0],
'The key \'%s\' should contain a \'.\'')
else:
self.assertEqual(str(err.exception),
str(("The key '%s' should contain a '.'", 'testing_no_dot')))
module_template = '''
__load__ = ['test', 'test_alias']
__func_alias__ = dict(test_alias='working_alias')

View File

@ -126,3 +126,26 @@ description:
patch('salt.modules.ansiblegate.importlib.import_module', lambda x: x):
with pytest.raises(LoaderError) as loader_error:
self.resolver.load_module('something.strange')
def test_virtual_function_no_ansible_installed(self):
'''
Test Ansible module __virtual__ when ansible is not installed on the minion.
:return:
'''
with patch('salt.modules.ansiblegate.ansible', None):
assert ansible.__virtual__() == (False, 'Ansible is not installed on this system')
@patch('salt.modules.ansiblegate.ansible', MagicMock())
@patch('salt.modules.ansiblegate.list', MagicMock())
@patch('salt.modules.ansiblegate._set_callables', MagicMock())
@patch('salt.modules.ansiblegate.AnsibleModuleCaller', MagicMock())
def test_virtual_function_ansible_is_installed(self):
'''
Test Ansible module __virtual__ when ansible is installed on the minion.
:return:
'''
resolver = MagicMock()
resolver.resolve = MagicMock()
resolver.resolve.install = MagicMock()
with patch('salt.modules.ansiblegate.AnsibleModuleResolver', resolver):
assert ansible.__virtual__() == (True, None)

View File

@ -669,8 +669,8 @@ Repository 'DUMMY' not found by its alias, number, or URI.
zypper_mock.assert_called_once_with(
'--no-refresh',
'install',
'--name',
'--auto-agree-with-licenses',
'--name',
'--download-only',
'vim'
)
@ -699,8 +699,8 @@ Repository 'DUMMY' not found by its alias, number, or URI.
zypper_mock.assert_called_once_with(
'--no-refresh',
'install',
'--name',
'--auto-agree-with-licenses',
'--name',
'--download-only',
'vim'
)
@ -724,8 +724,8 @@ Repository 'DUMMY' not found by its alias, number, or URI.
zypper_mock.assert_called_once_with(
'--no-refresh',
'install',
'--name',
'--auto-agree-with-licenses',
'--name',
'patch:SUSE-PATCH-1234'
)
self.assertDictEqual(ret, {"vim": {"old": "1.1", "new": "1.2"}})

View File

@ -815,7 +815,7 @@ class TestFileState(TestCase, LoaderModuleMockMixin):
'comment': comt,
'result': None,
'pchanges': p_chg,
'changes': {'/etc/grub.conf': {'directory': 'new'}}
'changes': {}
})
self.assertDictEqual(filestate.directory(name,
user=user,
@ -841,6 +841,11 @@ class TestFileState(TestCase, LoaderModuleMockMixin):
ret)
recurse = ['ignore_files', 'ignore_dirs']
ret.update({'comment': 'Must not specify "recurse" '
'options "ignore_files" and '
'"ignore_dirs" at the same '
'time.',
'pchanges': {}})
with patch.object(os.path, 'isdir', mock_t):
self.assertDictEqual(filestate.directory
(name, user=user,