diff --git a/conf/minion b/conf/minion index 52443204cb4c..3ba8ba009531 100644 --- a/conf/minion +++ b/conf/minion @@ -377,6 +377,10 @@ #include: # - /etc/salt/extra_config # - /etc/roles/webserver + +# The syndic minion can verify that it is talking to the correct master via the +# key fingerprint of the higher-level master with the "syndic_finger" config. +#syndic_finger: '' # # # diff --git a/doc/ref/configuration/master.rst b/doc/ref/configuration/master.rst index d58959936c73..bb4659fd51e7 100644 --- a/doc/ref/configuration/master.rst +++ b/doc/ref/configuration/master.rst @@ -2236,6 +2236,26 @@ configuration is the same as :conf_master:`file_roots`: prod: - /srv/pillar/prod +.. conf_master:: pillar_opts + +``pillar_opts`` +--------------- + +Default: ``False`` + +The ``pillar_opts`` option adds the master configuration file data to a dict in +the pillar called ``master``. This can be used to set simple configurations in +the master config file that can then be used on minions. + +Note that setting this option to ``True`` means the master config file will be +included in all minion's pillars. While this makes global configuration of services +and systems easy, it may not be desired if sensitive data is stored in the master +configuration. + +.. code-block:: yaml + + pillar_opts: False + .. _master-configuration-ext-pillar: .. conf_master:: ext_pillar diff --git a/doc/ref/configuration/minion.rst b/doc/ref/configuration/minion.rst index ed89a272d98e..3ee793536c25 100644 --- a/doc/ref/configuration/minion.rst +++ b/doc/ref/configuration/minion.rst @@ -909,6 +909,20 @@ what you are doing! Transports are explained in :ref:`Salt Transports transport: zeromq +.. conf_minion:: syndic_finger + +``syndic_finger`` +----------------- + +Default: ``''`` + +The key fingerprint of the higher-level master for the syndic to verify it is +talking to the intended master. + +.. code-block:: yaml + + syndic_finger: 'ab:30:65:2a:d6:9e:20:4f:d8:b2:f3:a7:d4:65:50:10' + Minion Module Management ======================== diff --git a/doc/topics/troubleshooting/yaml_idiosyncrasies.rst b/doc/topics/troubleshooting/yaml_idiosyncrasies.rst index beba8725ac7d..a6d8496eb4ae 100644 --- a/doc/topics/troubleshooting/yaml_idiosyncrasies.rst +++ b/doc/topics/troubleshooting/yaml_idiosyncrasies.rst @@ -341,3 +341,13 @@ string with quotes: ValueError: month must be in 1..12 >>> yaml.safe_load('"4017-16-20"') '4017-16-20' + + +Keys Limited to 1024 Characters +=============================== + +Simple keys are limited to a single line and cannot be longer that 1024 characters. +This is a limitation from PyYaml, as seen in a comment in `PyYAML's code`_, and +applies to anything parsed by YAML in Salt. + +.. _PyYAML's code: http://pyyaml.org/browser/pyyaml/trunk/lib/yaml/scanner.py#L91 diff --git a/doc/topics/tutorials/standalone_minion.rst b/doc/topics/tutorials/standalone_minion.rst index 33772139c4c7..367ba72e3674 100644 --- a/doc/topics/tutorials/standalone_minion.rst +++ b/doc/topics/tutorials/standalone_minion.rst @@ -17,6 +17,20 @@ things: Otherwise, it will attempt to connect to a master and fail. The salt-call command stands on its own and does not need the salt-minion daemon. + +Minion Configuration +-------------------- + +Throughout this document there are several references to setting different +options to configure a masterless Minion. Salt Minions are easy to configure +via a configuration file that is located, by default, in ``/etc/salt/minion``. +Note, however, that on FreeBSD systems, the minion configuration file is located +in ``/usr/local/etc/salt/minion``. + +You can learn more about minion configuration options in the +:ref:`Configuring the Salt Minion ` docs. + + Telling Salt Call to Run Masterless =================================== @@ -39,7 +53,6 @@ Now the salt-call command will not look for a master and will assume that the local system has all of the file and pillar resources. - Running States Masterless ========================= @@ -81,6 +94,7 @@ it unnecessary to change the configuration file: salt-call state.apply --local + External Pillars ================ diff --git a/pkg/suse/salt-minion b/pkg/suse/salt-minion index 957b2aa7069b..32ed5bb517ae 100644 --- a/pkg/suse/salt-minion +++ b/pkg/suse/salt-minion @@ -55,7 +55,7 @@ RETVAL=0 start() { echo -n $"Starting salt-minion daemon: " if [ -f $SUSE_RELEASE ]; then - startproc -f -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS + startproc -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS rc_status -v elif [ -e $DEBIAN_VERSION ]; then if [ -f $LOCKFILE ]; then diff --git a/salt/auth/pam.py b/salt/auth/pam.py index cedd5b609011..1e395a57ff87 100644 --- a/salt/auth/pam.py +++ b/salt/auth/pam.py @@ -117,6 +117,10 @@ class PamConv(Structure): PAM_AUTHENTICATE.restype = c_int PAM_AUTHENTICATE.argtypes = [PamHandle, c_int] + PAM_ACCT_MGMT = LIBPAM.pam_acct_mgmt + PAM_ACCT_MGMT.restype = c_int + PAM_ACCT_MGMT.argtypes = [PamHandle, c_int] + PAM_END = LIBPAM.pam_end PAM_END.restype = c_int PAM_END.argtypes = [PamHandle, c_int] @@ -171,6 +175,8 @@ def my_conv(n_messages, messages, p_response, app_data): return False retval = PAM_AUTHENTICATE(handle, 0) + if retval == 0: + PAM_ACCT_MGMT(handle, 0) PAM_END(handle, 0) return retval == 0 diff --git a/salt/beacons/diskusage.py b/salt/beacons/diskusage.py index b9b32d7fe045..89938244b11b 100644 --- a/salt/beacons/diskusage.py +++ b/salt/beacons/diskusage.py @@ -68,8 +68,7 @@ def beacon(config): ''' ret = [] - for diskusage in config: - mount = diskusage.keys()[0] + for mount in config: try: _current_usage = psutil.disk_usage(mount) @@ -79,7 +78,7 @@ def beacon(config): continue current_usage = _current_usage.percent - monitor_usage = diskusage[mount] + monitor_usage = config[mount] if '%' in monitor_usage: monitor_usage = re.sub('%', '', monitor_usage) monitor_usage = float(monitor_usage) diff --git a/salt/modules/etcd_mod.py b/salt/modules/etcd_mod.py index caec5d5b2953..78088c306af7 100644 --- a/salt/modules/etcd_mod.py +++ b/salt/modules/etcd_mod.py @@ -4,8 +4,11 @@ :depends: - python-etcd -In order to use an etcd server, a profile should be created in the master -configuration file: +Configuration +------------- + +To work with an etcd server you must configure an etcd profile. The etcd config +can be set in either the Salt Minion configuration file or in pillar: .. code-block:: yaml @@ -21,6 +24,17 @@ etcd.host: 127.0.0.1 etcd.port: 4001 + +.. note:: + + The etcd configuration can also be set in the Salt Master config file, + but in order to use any etcd configurations defined in the Salt Master + config, the :conf_master:`pillar_opts` must be set to ``True``. + + Be aware that setting ``pillar_opts`` to ``True`` has security implications + as this makes all master configuration settings available in all minion's + pillars. + ''' from __future__ import absolute_import diff --git a/salt/modules/glance.py b/salt/modules/glance.py index 48384c0bcfba..c7e3e4bef295 100644 --- a/salt/modules/glance.py +++ b/salt/modules/glance.py @@ -448,6 +448,12 @@ def image_schema(profile=None): ''' Returns names and descriptions of the schema "image"'s properties for this profile's instance of glance + + CLI Example: + + .. code-block:: bash + + salt '*' glance.image_schema ''' return schema_get('image', profile) @@ -459,6 +465,13 @@ def image_update(id=None, name=None, profile=None, **kwargs): # pylint: disable - min_ram (in MB) - protected (bool) - visibility ('public' or 'private') + + CLI Example: + + .. code-block:: bash + + salt '*' glance.image_update id=c2eb2eb0-53e1-4a80-b990-8ec887eae7df + salt '*' glance.image_update name=f16-jeos ''' if id: image = image_show(id=id, profile=profile) @@ -512,6 +525,12 @@ def schema_get(name, profile=None): - images - member - members + + CLI Example: + + .. code-block:: bash + + salt '*' glance.schema_get name=f16-jeos ''' g_client = _auth(profile) pformat = pprint.PrettyPrinter(indent=4).pformat diff --git a/salt/modules/nova.py b/salt/modules/nova.py index 61df8d081972..f64ace316ba1 100644 --- a/salt/modules/nova.py +++ b/salt/modules/nova.py @@ -562,6 +562,12 @@ def list_(profile=None): ''' To maintain the feel of the nova command line, this function simply calls the server_list function. + + CLI Example: + + .. code-block:: bash + + salt '*' nova.list ''' return server_list(profile=profile) diff --git a/salt/modules/postgres.py b/salt/modules/postgres.py index 0c3ec1ccf5dd..528fb8e85efb 100644 --- a/salt/modules/postgres.py +++ b/salt/modules/postgres.py @@ -346,6 +346,27 @@ def psql_query(query, user=None, host=None, port=None, maintenance_db=None, WITH updated AS (UPDATE pg_authid SET rolconnlimit = 2000 WHERE rolname = 'rolename' RETURNING rolconnlimit) SELECT * FROM updated; + query + The query string. + + user + Database username, if different from config or default. + + host + Database host, if different from config or default. + + port + Database port, if different from the config or default. + + maintenance_db + The database to run the query against. + + password + User password, if different from the config or default. + + runas + User to run the command as. + CLI Example: .. code-block:: bash diff --git a/salt/modules/rpm.py b/salt/modules/rpm.py index bcdf553ac91b..eb27bb588102 100644 --- a/salt/modules/rpm.py +++ b/salt/modules/rpm.py @@ -606,3 +606,29 @@ def version_cmp(ver1, ver2): log.warning("Failed to compare version '{0}' to '{1}' using RPM: {2}".format(ver1, ver2, exc)) return salt.utils.version_cmp(ver1, ver2) + + +def checksum(*paths): + ''' + Return if the signature of a RPM file is valid. + + CLI Example: + + .. code-block:: bash + + salt '*' lowpkg.checksum /path/to/package1.rpm + salt '*' lowpkg.checksum /path/to/package1.rpm /path/to/package2.rpm + ''' + ret = dict() + + if not paths: + raise CommandExecutionError("No package files has been specified.") + + for package_file in paths: + ret[package_file] = (bool(__salt__['file.file_exists'](package_file)) and + not __salt__['cmd.retcode'](["rpm", "-K", "--quiet", package_file], + ignore_retcode=True, + output_loglevel='trace', + python_shell=False)) + + return ret diff --git a/salt/modules/saltutil.py b/salt/modules/saltutil.py index 7586bf4a3d47..a99ff8d44da5 100644 --- a/salt/modules/saltutil.py +++ b/salt/modules/saltutil.py @@ -52,7 +52,7 @@ import salt.utils.url import salt.wheel from salt.exceptions import ( - SaltReqTimeoutError, SaltRenderError, CommandExecutionError + SaltReqTimeoutError, SaltRenderError, CommandExecutionError, SaltInvocationError ) __proxyenabled__ = ['*'] @@ -1046,7 +1046,7 @@ def runner(_fun, **kwargs): return rclient.cmd(_fun, kwarg=kwargs) -def wheel(_fun, **kwargs): +def wheel(_fun, *args, **kwargs): ''' Execute a wheel module (this function must be run on the master) @@ -1054,6 +1054,13 @@ def wheel(_fun, **kwargs): name The name of the function to run + + args + Any positional arguments to pass to the wheel function. A common example + of this would be the ``match`` arg needed for key functions. + + .. versionadded:: v2015.8.11 + kwargs Any keyword arguments to pass to the wheel function @@ -1061,10 +1068,33 @@ def wheel(_fun, **kwargs): .. code-block:: bash - salt '*' saltutil.wheel key.accept match=jerry + salt '*' saltutil.wheel key.accept jerry ''' - wclient = salt.wheel.WheelClient(__opts__) - return wclient.cmd(_fun, kwarg=kwargs) + if __opts__['__role'] == 'minion': + master_config = os.path.join(os.path.dirname(__opts__['conf_file']), + 'master') + master_opts = salt.config.client_config(master_config) + wheel_client = salt.wheel.WheelClient(master_opts) + else: + wheel_client = salt.wheel.WheelClient(__opts__) + + # The WheelClient cmd needs args, kwargs, and pub_data separated out from + # the "normal" kwargs structure, which at this point contains __pub_x keys. + pub_data = {} + valid_kwargs = {} + for key, val in six.iteritems(kwargs): + if key.startswith('__'): + pub_data[key] = val + else: + valid_kwargs[key] = val + + try: + ret = wheel_client.cmd(_fun, arg=args, pub_data=pub_data, kwarg=valid_kwargs) + except SaltInvocationError: + raise CommandExecutionError('This command can only be executed on a minion ' + 'that is located on the master.') + + return ret # this is the only way I could figure out how to get the REAL file_roots diff --git a/salt/modules/state.py b/salt/modules/state.py index 195c3567d90a..e9c39c6d3bda 100644 --- a/salt/modules/state.py +++ b/salt/modules/state.py @@ -207,7 +207,22 @@ def low(data, queue=False, **kwargs): return ret -def high(data, test=False, queue=False, **kwargs): +def _get_test_value(test=None, **kwargs): + ''' + Determine the correct value for the test flag. + ''' + ret = True + if test is None: + if salt.utils.test_mode(test=test, **kwargs): + ret = True + else: + ret = __opts__.get('test', None) + else: + ret = test + return ret + + +def high(data, test=None, queue=False, **kwargs): ''' Execute the compound calls stored in a single set of high data @@ -225,12 +240,7 @@ def high(data, test=False, queue=False, **kwargs): return conflict opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - elif test is not None: - opts['test'] = test - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) pillar = kwargs.get('pillar') pillar_enc = kwargs.get('pillar_enc') @@ -662,13 +672,7 @@ def highstate(test=None, opts = _get_opts(kwargs.get('localconfig')) - if test is None: - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) - else: - opts['test'] = test + opts['test'] = _get_test_value(test, **kwargs) if 'env' in kwargs: salt.utils.warn_until( @@ -876,12 +880,7 @@ def sls(mods, orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - elif test is not None: - opts['test'] = test - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) pillar = kwargs.get('pillar') pillar_enc = kwargs.get('pillar_enc') @@ -1003,10 +1002,7 @@ def top(topfn, return err orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) pillar = kwargs.get('pillar') pillar_enc = kwargs.get('pillar_enc') @@ -1119,10 +1115,7 @@ def sls_id( return conflict orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) if 'pillarenv' in kwargs: opts['pillarenv'] = kwargs['pillarenv'] st_ = salt.state.HighState(opts) @@ -1183,10 +1176,7 @@ def show_low_sls(mods, return conflict orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) if 'pillarenv' in kwargs: opts['pillarenv'] = kwargs['pillarenv'] st_ = salt.state.HighState(opts) @@ -1239,10 +1229,7 @@ def show_sls(mods, saltenv='base', test=None, queue=False, env=None, **kwargs): orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) pillar = kwargs.get('pillar') pillar_enc = kwargs.get('pillar_enc') @@ -1339,10 +1326,7 @@ def single(fun, name, test=None, queue=False, **kwargs): 'name': name}) orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) pillar = kwargs.get('pillar') pillar_enc = kwargs.get('pillar_enc') @@ -1398,7 +1382,7 @@ def clear_cache(): return ret -def pkg(pkg_path, pkg_sum, hash_type, test=False, **kwargs): +def pkg(pkg_path, pkg_sum, hash_type, test=None, **kwargs): ''' Execute a packaged state run, the packaged state run will exist in a tarball available locally. This packaged state @@ -1442,10 +1426,7 @@ def pkg(pkg_path, pkg_sum, hash_type, test=False, **kwargs): popts = _get_opts(kwargs.get('localconfig')) popts['fileclient'] = 'local' popts['file_roots'] = {} - if salt.utils.test_mode(test=test, **kwargs): - popts['test'] = True - else: - popts['test'] = __opts__.get('test', None) + popts['test'] = _get_test_value(test, **kwargs) envs = os.listdir(root) for fn_ in envs: full = os.path.join(root, fn_) diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py index 9276ddfa61d2..f4283de2ad63 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -769,6 +769,7 @@ def mod_repo(repo, **kwargs): # Modify added or existing repo according to the options cmd_opt = [] + global_cmd_opt = [] if 'enabled' in kwargs: cmd_opt.append(kwargs['enabled'] and '--enable' or '--disable') @@ -784,18 +785,18 @@ def mod_repo(repo, **kwargs): if 'gpgcheck' in kwargs: cmd_opt.append(kwargs['gpgcheck'] and '--gpgcheck' or '--no-gpgcheck') - if kwargs.get('gpgautoimport') is True: - cmd_opt.append('--gpg-auto-import-keys') - if 'priority' in kwargs: cmd_opt.append("--priority={0}".format(kwargs.get('priority', DEFAULT_PRIORITY))) if 'humanname' in kwargs: cmd_opt.append("--name='{0}'".format(kwargs.get('humanname'))) + if kwargs.get('gpgautoimport') is True: + global_cmd_opt.append('--gpg-auto-import-keys') + if cmd_opt: - cmd_opt.append(repo) - __zypper__.refreshable.xml.call('mr', *cmd_opt) + cmd_opt = global_cmd_opt + ['mr'] + cmd_opt + [repo] + __zypper__.refreshable.xml.call(*cmd_opt) # If repo nor added neither modified, error should be thrown if not added and not cmd_opt: @@ -1583,6 +1584,9 @@ def download(*packages, **kwargs): pkg_ret[key] = pkg_info if pkg_ret: + failed = [pkg for pkg in packages if pkg not in pkg_ret] + if failed: + pkg_ret['_error'] = ('The following package(s) failed to download: {0}'.format(', '.join(failed))) return pkg_ret raise CommandExecutionError( diff --git a/salt/runners/jobs.py b/salt/runners/jobs.py index def40976fccf..6e99d2b9f510 100644 --- a/salt/runners/jobs.py +++ b/salt/runners/jobs.py @@ -349,9 +349,13 @@ def list_jobs(ext_source=None, if search_target and _match: _match = False if 'Target' in ret[item]: - for key in salt.utils.split_input(search_target): - if fnmatch.fnmatch(ret[item]['Target'], key): - _match = True + targets = ret[item]['Target'] + if isinstance(targets, six.string_types): + targets = [targets] + for target in targets: + for key in salt.utils.split_input(search_target): + if fnmatch.fnmatch(target, key): + _match = True if search_function and _match: _match = False @@ -488,6 +492,33 @@ def print_job(jid, ext_source=None, outputter=None): return ret +def exit_success(jid, ext_source=None): + ''' + Check if a job has been executed and exit successfully + + jid + The jid to look up. + ext_source + The external job cache to use. Default: `None`. + + CLI Example: + .. code-block:: bash + salt-run jobs.exit_success 20160520145827701627 + ''' + ret = dict() + + data = lookup_jid( + jid, + ext_source=ext_source + ) + + for minion in data: + if "retcode" in data[minion]: + ret[minion] = True if not data[minion]['retcode'] else False + + return ret + + def last_run(ext_source=None, outputter=None, metadata=None, diff --git a/salt/states/etcd_mod.py b/salt/states/etcd_mod.py index c0f4ea5f8531..29406d74b9ac 100644 --- a/salt/states/etcd_mod.py +++ b/salt/states/etcd_mod.py @@ -10,11 +10,11 @@ This state module supports setting and removing keys from etcd. -Salt Master Configuration -------------------------- +Configuration +------------- -To work with an etcd server you must configure an etcd profile in the Salt -Master configuration, for example: +To work with an etcd server you must configure an etcd profile. The etcd config +can be set in either the Salt Minion configuration file or in pillar: .. code-block:: yaml @@ -22,14 +22,25 @@ etcd.host: 127.0.0.1 etcd.port: 4001 -You can also configure etcd without a profile however it is recommended that -you use profiles: +It is technically possible to configure etcd without using a profile, but this +is not considered to be a best practice, especially when multiple etcd servers +or clusters are available. .. code-block:: yaml etcd.host: 127.0.0.1 etcd.port: 4001 +.. note:: + + The etcd configuration can also be set in the Salt Master config file, + but in order to use any etcd configurations defined in the Salt Master + config, the :conf_master:`pillar_opts` must be set to ``True``. + + Be aware that setting ``pillar_opts`` to ``True`` has security implications + as this makes all master configuration settings available in all minion's + pillars. + Available Functions ------------------- diff --git a/salt/states/grains.py b/salt/states/grains.py index 8b5c264c9c2d..c44e0ae43bf9 100644 --- a/salt/states/grains.py +++ b/salt/states/grains.py @@ -10,16 +10,19 @@ Note: This does NOT override any grains set in the minion file. ''' +# Import Python libs from __future__ import absolute_import -from salt.defaults import DEFAULT_TARGET_DELIM import re +# Import Salt libs +from salt.defaults import DEFAULT_TARGET_DELIM + def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False): ''' Ensure that a grain is set - .. versionchanged:: 2016.3.0 + .. versionchanged:: v2015.8.2 name The grain name @@ -27,14 +30,16 @@ def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False): value The value to set on the grain - :param force: If force is True, the existing grain will be overwritten + force + If force is True, the existing grain will be overwritten regardless of its existing or provided value type. Defaults to False - .. versionadded:: 2016.3.0 + .. versionadded:: v2015.8.2 - :param delimiter: A delimiter different from the default can be provided. + delimiter + A delimiter different from the default can be provided. - .. versionadded:: 2016.3.0 + .. versionadded:: v2015.8.2 It is now capable to set a grain to a complex value (ie. lists and dicts) and supports nested grains as well. @@ -59,11 +64,11 @@ def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False): - name: icinga:Apache SSL - value: - command: check_https - - params: -H localhost -p 443 -S + - params: -H localhost -p 443 -S with,a,custom,delimiter: grains.present: - - value: yay + - value: yay - delimiter: , ''' name = re.sub(delimiter, DEFAULT_TARGET_DELIM, name) @@ -106,9 +111,10 @@ def list_present(name, value, delimiter=DEFAULT_TARGET_DELIM): value The value is present in the list type grain. - :param delimiter: A delimiter different from the default ``:`` can be provided. + delimiter + A delimiter different from the default ``:`` can be provided. - .. versionadded:: 2016.3.0 + .. versionadded:: v2015.8.2 The grain should be `list type `_ @@ -198,9 +204,10 @@ def list_absent(name, value, delimiter=DEFAULT_TARGET_DELIM): value The value to delete from the grain list. - :param delimiter: A delimiter different from the default ``:`` can be provided. + delimiter + A delimiter different from the default ``:`` can be provided. - .. versionadded:: 2016.3.0 + .. versionadded:: v2015.8.2 The grain should be `list type `_ @@ -273,19 +280,23 @@ def absent(name, name The grain name - :param destructive: If destructive is True, delete the entire grain. If + destructive + If destructive is True, delete the entire grain. If destructive is False, set the grain's value to None. Defaults to False. - :param force: If force is True, the existing grain will be overwritten + force + If force is True, the existing grain will be overwritten regardless of its existing or provided value type. Defaults to False - .. versionadded:: 2016.3.0 + .. versionadded:: v2015.8.2 + + delimiter + A delimiter different from the default can be provided. - :param delimiter: A delimiter different from the default can be provided. + .. versionadded:: v2015.8.2 - .. versionadded:: 2016.3.0 + .. versionchanged:: v2015.8.2 - .. versionchanged:: 2016.3.0 This state now support nested grains and complex values. It is also more conservative: if a grain has a value that is a list or a dict, it will not be removed unless the `force` parameter is True. @@ -367,13 +378,15 @@ def append(name, value, convert=False, value The value to append - :param convert: If convert is True, convert non-list contents into a list. + convert + If convert is True, convert non-list contents into a list. If convert is False and the grain contains non-list contents, an error is given. Defaults to False. - :param delimiter: A delimiter different from the default can be provided. + delimiter + A delimiter different from the default can be provided. - .. versionadded:: 2016.3.0 + .. versionadded:: v2015.8.2 .. code-block:: yaml diff --git a/salt/states/pkgrepo.py b/salt/states/pkgrepo.py index ac2e90bad4e2..f1363a94f5f2 100644 --- a/salt/states/pkgrepo.py +++ b/salt/states/pkgrepo.py @@ -274,6 +274,11 @@ def managed(name, ppa=None, **kwargs): ret['result'] = False ret['comment'] = 'You may not use both "keyid"/"keyserver" and ' \ '"key_url" argument.' + if 'repo' in kwargs: + ret['result'] = False + ret['comment'] = ('\'repo\' is not a supported argument for this ' + 'state. The \'name\' argument is probably what was ' + 'intended.') return ret repo = name diff --git a/salt/states/service.py b/salt/states/service.py index 934ac4751bfe..88e8951eab26 100644 --- a/salt/states/service.py +++ b/salt/states/service.py @@ -65,13 +65,21 @@ # Import Salt libs from salt.exceptions import CommandExecutionError +__virtualname__ = 'service' + def __virtual__(): ''' Only make these states available if a service provider has been detected or assigned for this minion ''' - return 'service.start' in __salt__ + if 'service.start' in __salt__: + return __virtualname__ + else: + return (False, 'No service execution module loaded: ' + 'check support for service management on {0} ' + ''.format(__grains__.get('osfinger', __grains__['os'])) + ) def _enabled_used_error(ret): @@ -283,7 +291,7 @@ def _available(name, ret): def running(name, enable=None, sig=None, init_delay=None, **kwargs): ''' - Verify that the service is running + Ensure that the service is running name The name of the init or rc script used to manage the service @@ -473,7 +481,7 @@ def dead(name, enable=None, sig=None, **kwargs): def enabled(name, **kwargs): ''' - Verify that the service is enabled on boot, only use this state if you + Ensure that the service is enabled on boot, only use this state if you don't want to manage the running process, remember that if you want to enable a running service to use the enable: True option for the running or dead function. @@ -492,7 +500,7 @@ def enabled(name, **kwargs): def disabled(name, **kwargs): ''' - Verify that the service is disabled on boot, only use this state if you + Ensure that the service is disabled on boot, only use this state if you don't want to manage the running process, remember that if you want to disable a service to use the enable: False option for the running or dead function. diff --git a/tests/integration/modules/saltutil.py b/tests/integration/modules/saltutil.py new file mode 100644 index 000000000000..ebce06f96124 --- /dev/null +++ b/tests/integration/modules/saltutil.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +''' +Integration tests for the saltutil module. +''' + +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing libs +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt libs +import integration + + +class SaltUtilModuleTest(integration.ModuleCase): + ''' + Testcase for the saltutil execution module + ''' + + # Tests for the wheel function + + def test_wheel_just_function(self): + ''' + Tests using the saltutil.wheel function when passing only a function. + ''' + ret = self.run_function('saltutil.wheel', ['minions.connected']) + self.assertEqual(ret, ['minion', 'sub_minion']) + + def test_wheel_with_arg(self): + ''' + Tests using the saltutil.wheel function when passing a function and an arg. + ''' + ret = self.run_function('saltutil.wheel', ['key.list', 'minion']) + self.assertEqual(ret, {}) + + def test_wheel_no_arg_raise_error(self): + ''' + Tests using the saltutil.wheel function when passing a function that requires + an arg, but one isn't supplied. + ''' + self.assertRaises(TypeError, 'saltutil.wheel', ['key.list']) + + def test_wheel_with_kwarg(self): + ''' + Tests using the saltutil.wheel function when passing a function and a kwarg. + This function just generates a key pair, but doesn't do anything with it. We + just need this for testing purposes. + ''' + ret = self.run_function('saltutil.wheel', ['key.gen'], keysize=1024) + self.assertIn('pub', ret) + self.assertIn('priv', ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(SaltUtilModuleTest) diff --git a/tests/integration/modules/sysmod.py b/tests/integration/modules/sysmod.py index 7f41818747c4..72430cf853e6 100644 --- a/tests/integration/modules/sysmod.py +++ b/tests/integration/modules/sysmod.py @@ -66,6 +66,7 @@ def test_valid_docs(self): 'runtests_decorators.depends_will_fallback', 'runtests_decorators.missing_depends', 'runtests_decorators.missing_depends_will_fallback', + 'swift.head', 'yumpkg.expand_repo_def', 'yumpkg5.expand_repo_def', 'container_resource.run', diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py index 4e735cde261a..c0ba3e95e642 100644 --- a/tests/unit/modules/zypper_test.py +++ b/tests/unit/modules/zypper_test.py @@ -9,6 +9,7 @@ # Import Salt Testing Libs from salttesting import TestCase, skipIf from salttesting.mock import ( + Mock, MagicMock, patch, NO_MOCK, @@ -413,6 +414,39 @@ def test_repo_value_info(self): self.assertEqual(r_info['enabled'], alias == 'SLE12-SP1-x86_64-Update') self.assertEqual(r_info['autorefresh'], alias == 'SLE12-SP1-x86_64-Update') + def test_modify_repo_gpg_auto_import_keys_parameter_position(self): + ''' + Tests if when modifying a repo, --gpg-auto-import-keys is a global option + + :return: + ''' + zypper_patcher = patch.multiple( + 'salt.modules.zypper', + **{ + '_get_configured_repos': Mock( + **{ + 'return_value.sections.return_value': ['mock-repo-name'] + } + ), + '__zypper__': Mock(), + 'get_repo': Mock() + } + ) + with zypper_patcher: + zypper.mod_repo( + 'mock-repo-name', + **{ + 'disabled': False, + 'url': 'http://repo.url/some/path', + 'gpgkey': 'http://repo.key', + 'refresh': True, + 'gpgautoimport': True + } + ) + zypper.__zypper__.refreshable.xml.call.assert_called_once_with( + '--gpg-auto-import-keys', 'mr', '--refresh', 'mock-repo-name' + ) + if __name__ == '__main__': from integration import run_tests run_tests(ZypperTestCase, needs_daemon=False) diff --git a/tests/unit/runners/jobs_test.py b/tests/unit/runners/jobs_test.py new file mode 100644 index 000000000000..c2893f9e947b --- /dev/null +++ b/tests/unit/runners/jobs_test.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +''' +unit tests for the jobs runner +''' + +# Import Python Libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.helpers import ensure_in_syspath +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + patch +) + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.runners import jobs +import salt.minion + +jobs.__opts__ = {'ext_job_cache': None, 'master_job_cache': 'local_cache'} +jobs.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class JobsTest(TestCase): + ''' + Validate the jobs runner + ''' + def test_list_jobs_with_search_target(self): + ''' + test jobs.list_jobs runner with search_target args + ''' + mock_jobs_cache = { + '20160524035503086853': {'Arguments': [], + 'Function': 'test.ping', + 'StartTime': '2016, May 24 03:55:03.086853', + 'Target': 'node-1-1.com', + 'Target-type': 'glob', + 'User': 'root'}, + '20160524035524895387': {'Arguments': [], + 'Function': 'test.ping', + 'StartTime': '2016, May 24 03:55:24.895387', + 'Target': ['node-1-2.com', 'node-1-1.com'], + 'Target-type': 'list', + 'User': 'sudo_ubuntu'} + } + + def return_mock_jobs(): + return mock_jobs_cache + + class MockMasterMinion(object): + + returners = {'local_cache.get_jids': return_mock_jobs} + + def __init__(self, *args, **kwargs): + pass + + returns = {'all': mock_jobs_cache, + 'node-1-1.com': mock_jobs_cache, + 'node-1-2.com': {'20160524035524895387': + mock_jobs_cache['20160524035524895387']}, + 'non-existant': {}} + + with patch.object(salt.minion, 'MasterMinion', MockMasterMinion): + self.assertEqual(jobs.list_jobs(), returns['all']) + + self.assertEqual(jobs.list_jobs(search_target=['node-1-1*', + 'node-1-2*']), + returns['all']) + + self.assertEqual(jobs.list_jobs(search_target='node-1-1.com'), + returns['node-1-1.com']) + + self.assertEqual(jobs.list_jobs(search_target='node-1-2.com'), + returns['node-1-2.com']) + + self.assertEqual(jobs.list_jobs(search_target='non-existant'), + returns['non-existant']) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(JobsTest, needs_daemon=False)