diff --git a/doc/ref/cli/salt-call.rst b/doc/ref/cli/salt-call.rst index 82d4b8a988..c6d89af79e 100644 --- a/doc/ref/cli/salt-call.rst +++ b/doc/ref/cli/salt-call.rst @@ -15,6 +15,14 @@ Description The salt-call command is used to run module functions locally on a minion instead of executing them from the master. +salt-call is used to run a :ref:`Standalone Minion `, +and was originally created for :ref:`troubleshooting `. +Be aware that ``salt-call`` commands execute from the current user's shell +context, while ``salt`` commands execute from the system's default context. + +By default, the Salt Master is contacted to retrieve state files and other resources +during execution unless the ``--local`` option is specified.:w + Options ======= diff --git a/doc/ref/cli/salt-cloud.rst b/doc/ref/cli/salt-cloud.rst index 2c452e6759..863670e36b 100644 --- a/doc/ref/cli/salt-cloud.rst +++ b/doc/ref/cli/salt-cloud.rst @@ -87,11 +87,6 @@ Execution Options for this influx of vm creation. When creating large groups of VMs watch the cloud provider carefully. -.. option:: -Q, --query - - Execute a query and print out information about all cloud VMs. Can be used - in conjunction with -m to display only information about the specified map. - .. option:: -u, --update-bootstrap Update salt-bootstrap to the latest develop version on GitHub. diff --git a/doc/ref/configuration/master.rst b/doc/ref/configuration/master.rst index db6dcea6c2..da2d021408 100644 --- a/doc/ref/configuration/master.rst +++ b/doc/ref/configuration/master.rst @@ -2437,3 +2437,15 @@ List of git repositories to include with the local repo. win_gitrepos: - 'https://github.com/saltstack/salt-winrepo.git' + +To specify a specific revision of the repository, preface the +repository location with a commit ID: + +.. code-block:: yaml + + win_gitrepos: + - ' https://github.com/saltstack/salt-winrepo.git' + +Replacing ```` with the ID from GitHub. Specifying a commit +ID is useful if you need to revert to a previous version if an error +is introduced in the latest version. diff --git a/doc/ref/configuration/minion.rst b/doc/ref/configuration/minion.rst index 279d06fe0d..017514f0a3 100644 --- a/doc/ref/configuration/minion.rst +++ b/doc/ref/configuration/minion.rst @@ -313,6 +313,22 @@ executed. By default this feature is disabled, to enable set cache_jobs to cache_jobs: False +.. conf_minion:: grains_cache + +``grains_cache`` +---------------- + +Default: ``False`` + +The minion can locally cache grain data instead of refreshing the data +each time the grain is referenced. By default this feature is disabled, +to enable set grains_cache to ``True``. + +.. code-block:: yaml + + cache_jobs: False + + .. conf_minion:: sock_dir ``sock_dir`` diff --git a/doc/ref/file_server/index.rst b/doc/ref/file_server/index.rst index 0b03f44e44..47ad2c10b5 100644 --- a/doc/ref/file_server/index.rst +++ b/doc/ref/file_server/index.rst @@ -24,6 +24,21 @@ The cp module is the home of minion side file server operations. The cp module is used by the Salt state system, salt-cp, and can be used to distribute files presented by the Salt file server. +Escaping Special Characters +``````````````````````````` + +The ``salt://`` url format can potentially contain a query string, for example +``salt://dir/file.txt?saltenv=base``. You can prevent the fileclient/fileserver from +interpreting ``?`` as the initial token of a query string by referencing the file +with ``salt://|`` rather than ``salt://``. + +.. code-block:: yaml + + /etc/marathon/conf/?checkpoint: + file.managed: + - source: salt://|hw/config/?checkpoint + - makedirs: True + Environments ```````````` diff --git a/doc/ref/modules/all/salt.modules.ipmi.rst b/doc/ref/modules/all/salt.modules.ipmi.rst index 02d108b44b..2a2cb94660 100644 --- a/doc/ref/modules/all/salt.modules.ipmi.rst +++ b/doc/ref/modules/all/salt.modules.ipmi.rst @@ -3,4 +3,4 @@ salt.modules.ipmi ================= .. automodule:: salt.modules.ipmi - :members + :members: diff --git a/doc/ref/states/all/index.rst b/doc/ref/states/all/index.rst index 5daa3b3f4b..afe0c3d918 100644 --- a/doc/ref/states/all/index.rst +++ b/doc/ref/states/all/index.rst @@ -158,6 +158,7 @@ Full list of builtin state modules sysrc test timezone + tls tomcat tuned uptime diff --git a/doc/ref/states/requisites.rst b/doc/ref/states/requisites.rst index 662f89df69..c3a90e6721 100644 --- a/doc/ref/states/requisites.rst +++ b/doc/ref/states/requisites.rst @@ -554,7 +554,7 @@ at the end of a state run, after all states have completed. configure-apache2: file.managed: - - path: /etc/apache2/apache2.conf + - name: /etc/apache2/apache2.conf - source: salt://apache2/apache2.conf This example will cause apache2 to be restarted when the apache2.conf file is @@ -568,7 +568,7 @@ changed, but the apache2 restart will happen at the end of the state run. configure-apache2: file.managed: - - path: /etc/apache2/apache2.conf + - name: /etc/apache2/apache2.conf - source: salt://apache2/apache2.conf - listen_in: - service: apache2 @@ -593,7 +593,7 @@ same privileges as the salt-minion. comment-repo: file.replace: - - path: /etc/yum.repos.d/fedora.repo + - name: /etc/yum.repos.d/fedora.repo - pattern: ^enabled=0 - repl: enabled=1 - check_cmd: diff --git a/doc/topics/cloud/vsphere.rst b/doc/topics/cloud/vsphere.rst index fc38058997..4f3368d141 100644 --- a/doc/topics/cloud/vsphere.rst +++ b/doc/topics/cloud/vsphere.rst @@ -104,7 +104,7 @@ added to the folder the original VM belongs to. host ---- -The MOR of the host where the vm should be registered. +The MOR of the host where the vm should be registered. If not specified: * if resourcepool is not specified, the current host is used. diff --git a/doc/topics/development/external_pillars.rst b/doc/topics/development/external_pillars.rst index c06362edd8..d0e9eb3952 100644 --- a/doc/topics/development/external_pillars.rst +++ b/doc/topics/development/external_pillars.rst @@ -1,6 +1,8 @@ -=================== +.. _external-pillars: + +================ External Pillars -=================== +================ Salt provides a mechanism for generating pillar data by calling external pillar interfaces. This document will describe an outline of an ext_pillar diff --git a/doc/topics/eauth/index.rst b/doc/topics/eauth/index.rst index 35557f4399..36215b18fb 100644 --- a/doc/topics/eauth/index.rst +++ b/doc/topics/eauth/index.rst @@ -212,7 +212,7 @@ To configure an LDAP group, append a ``%`` to the ID: .. code-block:: yaml external_auth: - ldap: - test_ldap_group%: - - '*': - - test.echo + ldap: + test_ldap_group%: + - '*': + - test.echo diff --git a/doc/topics/installation/windows.rst b/doc/topics/installation/windows.rst index b5a30fa488..6ab4f306d8 100644 --- a/doc/topics/installation/windows.rst +++ b/doc/topics/installation/windows.rst @@ -43,8 +43,9 @@ minion exe>` should match the contents of the corresponding md5 file. * 2014.7.0 * Salt-Minion-2014.7.0-1-win32-Setup.exe | md5 * Salt-Minion-2014.7.0-AMD64-Setup.exe | md5 - .. note:: - The 2014.7.0 installers have been removed because of a regression. Please use the 2014.7.1 release instead. + .. note:: + + The 2014.7.0 installers have been removed because of a regression. Please use the 2014.7.1 release instead. * 2014.1.13 * `Salt-Minion-2014.1.13-x86-Setup.exe `__ | `md5 `__ diff --git a/doc/topics/troubleshooting/minion.rst b/doc/topics/troubleshooting/minion.rst index bd7d328372..d15f559bad 100644 --- a/doc/topics/troubleshooting/minion.rst +++ b/doc/topics/troubleshooting/minion.rst @@ -66,6 +66,8 @@ check that no additional access control system such as `SELinux`_ or .. _`SELinux`: https://en.wikipedia.org/wiki/Security-Enhanced_Linux .. _`AppArmor`: http://wiki.apparmor.net/index.php/Main_Page +.. _troubleshooting-minion-salt-call: + Using salt-call =============== diff --git a/doc/topics/tutorials/pillar.rst b/doc/topics/tutorials/pillar.rst index 56fab80937..7bfc6df77c 100644 --- a/doc/topics/tutorials/pillar.rst +++ b/doc/topics/tutorials/pillar.rst @@ -34,9 +34,9 @@ Variables: minions can be defined in pillar and then accessed inside sls formulas and template files. Arbitrary Data: - Pillar can contain any basic data structure, so a list of values, or a - key/value store can be defined making it easy to iterate over a group - of values in sls formulas + Pillar can contain any basic data structure in dictionary format, + so a key/value store can be defined making it easy to iterate over a group + of values in sls formulas. Pillar is therefore one of the most important systems when using Salt. This walkthrough is designed to get a simple Pillar up and running in a few minutes @@ -309,12 +309,6 @@ line: salt '*' state.sls my_sls_file pillar='{"hello": "world"}' -Lists can be passed in pillar as well: - -.. code-block:: bash - - salt '*' state.highstate pillar='["foo", "bar", "baz"]' - .. note:: If a key is passed on the command line that already exists on the minion, diff --git a/doc/topics/tutorials/standalone_minion.rst b/doc/topics/tutorials/standalone_minion.rst index 0ccffa1c85..5badda2f22 100644 --- a/doc/topics/tutorials/standalone_minion.rst +++ b/doc/topics/tutorials/standalone_minion.rst @@ -1,3 +1,5 @@ +.. _tutorial-standalone-minion: + ================= Standalone Minion ================= @@ -77,4 +79,10 @@ it unnecessary to change the configuration file: .. code-block:: bash - salt-call state.highstate --local \ No newline at end of file + salt-call state.highstate --local + +External Pillars +================ + +:ref:`External pillars ` are supported when running in masterless mode. + diff --git a/doc/topics/tutorials/states_pt1.rst b/doc/topics/tutorials/states_pt1.rst index af2a2c075d..2dbe18ba57 100644 --- a/doc/topics/tutorials/states_pt1.rst +++ b/doc/topics/tutorials/states_pt1.rst @@ -185,8 +185,8 @@ and all changes made. salt-minion -l debug Run the minion in the foreground - By not starting the minion in daemon mode (:option:`-d `) one can view any output from the minion as it works: + By not starting the minion in daemon mode (:option:`-d `) + one can view any output from the minion as it works: .. code-block:: bash diff --git a/salt/beacons/inotify.py b/salt/beacons/inotify.py index 6af1cf7c4c..f11b544c09 100644 --- a/salt/beacons/inotify.py +++ b/salt/beacons/inotify.py @@ -153,24 +153,26 @@ def beacon(config): The mask list can contain the following events (the default mask is create, delete, and modify): - * access File accessed - * attrib File metadata changed - * close_nowrite Unwritable file closed - * close_write Writable file closed - * create File created in watched directory - * delete File deleted from watched directory - * delete_self Watched file or directory deleted - * modify File modified - * moved_from File moved out of watched directory - * moved_to File moved into watched directory - * move_self Watched file moved - * open File opened + + * access File accessed + * attrib File metadata changed + * close_nowrite Unwritable file closed + * close_write Writable file closed + * create File created in watched directory + * delete File deleted from watched directory + * delete_self Watched file or directory deleted + * modify File modified + * moved_from File moved out of watched directory + * moved_to File moved into watched directory + * move_self Watched file moved + * open File opened The mask can also contain the following options: - * dont_follow Don't dereference symbolic links - * excl_unlink Omit events for children after they have been unlinked - * oneshot Remove watch after one event - * onlydir Operate only if name is directory + + * dont_follow Don't dereference symbolic links + * excl_unlink Omit events for children after they have been unlinked + * oneshot Remove watch after one event + * onlydir Operate only if name is directory recurse: Recursively watch files in the directory diff --git a/salt/cli/ssh.py b/salt/cli/ssh.py index 5812300bb4..8e22b166c7 100644 --- a/salt/cli/ssh.py +++ b/salt/cli/ssh.py @@ -13,6 +13,7 @@ class SaltSSH(parsers.SaltSSHOptionParser): def run(self): self.parse_args() + self.setup_logfile_logger() ssh = salt.client.ssh.SSH(self.config) ssh.run() diff --git a/salt/minion.py b/salt/minion.py index a402674205..fbc3def9e7 100644 --- a/salt/minion.py +++ b/salt/minion.py @@ -720,7 +720,8 @@ class Minion(MinionBase): ' {0}'.format(opts['master'])) if opts['master_shuffle']: shuffle(opts['master']) - elif isinstance(opts['master'], str): + # if opts['master'] is a str and we have never created opts['master_list'] + elif isinstance(opts['master'], str) and ('master_list' not in opts): # We have a string, but a list was what was intended. Convert. # See issue 23611 for details opts['master'] = list(opts['master']) diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py index 50071244ac..1fc55595ea 100644 --- a/salt/modules/aptpkg.py +++ b/salt/modules/aptpkg.py @@ -1281,13 +1281,18 @@ def get_repo(repo, **kwargs): ppa_name, dist) else: if HAS_SOFTWAREPROPERTIES: - if hasattr(softwareproperties.ppa, 'PPAShortcutHandler'): - repo = softwareproperties.ppa.PPAShortcutHandler(repo).expand( - __grains__['lsb_distrib_codename'])[0] - else: - repo = softwareproperties.ppa.expand_ppa_line( - repo, - __grains__['lsb_distrib_codename'])[0] + try: + if hasattr(softwareproperties.ppa, 'PPAShortcutHandler'): + repo = softwareproperties.ppa.PPAShortcutHandler( + repo).expand(dist)[0] + else: + repo = softwareproperties.ppa.expand_ppa_line( + repo, + dist)[0] + except NameError as name_error: + raise CommandExecutionError( + 'Could not find ppa {0}: {1}'.format(repo, name_error) + ) else: repo = LP_SRC_FORMAT.format(owner_name, ppa_name, dist) diff --git a/salt/modules/cmdmod.py b/salt/modules/cmdmod.py index 8e64cd7cbc..ebc0e3842b 100644 --- a/salt/modules/cmdmod.py +++ b/salt/modules/cmdmod.py @@ -745,7 +745,9 @@ def shell(cmd, .. versionadded:: 2015.5.0 - .. warning:: This passes the cmd argument directly to the shell + .. warning:: + + This passes the cmd argument directly to the shell without any further processing! Be absolutely sure that you have properly santized the command passed to this function and do not use untrusted inputs. diff --git a/salt/modules/dockerio.py b/salt/modules/dockerio.py index dd1ee9c62a..d366c9458f 100644 --- a/salt/modules/dockerio.py +++ b/salt/modules/dockerio.py @@ -575,7 +575,8 @@ def create_container(image, volumes_from=None, name=None, cpu_shares=None, - cpuset=None): + cpuset=None, + binds=None): ''' Create a new container @@ -594,10 +595,26 @@ def create_container(image, ports port redirections ``({'222': {}})`` volumes - list of volume mappings:: + list of volume mappings in either local volume, bound volume, or read-only + bound volume form:: - (['/mountpoint/in/container:/guest/foo', '/same/path/mounted/point']) + (['/var/lib/mysql/', '/usr/local/etc/ssl:/etc/ssl', '/etc/passwd:/etc/passwd:ro']) + binds + complete dictionary of bound volume mappings:: + { '/usr/local/etc/ssl/certs/internal.crt': { + 'bind': '/etc/ssl/certs/com.example.internal.crt', + 'ro': True + }, + '/var/lib/mysql': { + 'bind': '/var/lib/mysql/', + 'ro': False + } + } + + This dictionary is suitable for feeding directly into the Docker API, and all + keys are required. + (see http://docker-py.readthedocs.org/en/latest/volumes/) tty attach ttys, Default is ``False`` stdin_open @@ -616,23 +633,31 @@ def create_container(image, salt '*' docker.create_container o/ubuntu volumes="['/s','/m:/f']" ''' + log.trace("modules.dockerio.create_container() called for image " + image) status = base_status.copy() client = _get_client() + + # In order to permit specification of bind volumes in the volumes field, + # we'll look through it for bind-style specs and move them. This is purely + # for CLI convenience and backwards-compatibility, as states.dockerio + # should parse volumes before this, and the binds argument duplicates this. + # N.B. this duplicates code in states.dockerio._parse_volumes() + if isinstance(volumes, list): + for volume in volumes: + if ':' in volume: + volspec = volume.split(':') + source = volspec[0] + target = volspec[1] + ro = False + try: + if len(volspec) > 2: + ro = volspec[2] == "ro" + except IndexError: + pass + binds[source] = {'bind': target, 'ro': ro} + volumes.remove(volume) + try: - mountpoints = {} - binds = {} - # create empty mountpoints for them to be - # editable - # either we have a list of guest or host:guest - if isinstance(volumes, list): - for mountpoint in volumes: - mounted = mountpoint - if ':' in mountpoint: - parts = mountpoint.split(':') - mountpoint = parts[1] - mounted = parts[0] - mountpoints[mountpoint] = {} - binds[mounted] = mountpoint container_info = client.create_container( image=image, command=command, @@ -645,12 +670,14 @@ def create_container(image, ports=ports, environment=environment, dns=dns, - volumes=mountpoints, + volumes=volumes, volumes_from=volumes_from, name=name, cpu_shares=cpu_shares, - cpuset=cpuset + cpuset=cpuset, + host_config=docker.utils.create_host_config(binds=binds) ) + log.trace("docker.client.create_container returned: " + str(container_info)) container = container_info['Id'] callback = _valid comment = 'Container created' @@ -660,8 +687,9 @@ def create_container(image, } __salt__['mine.send']('docker.get_containers', host=True) return callback(status, id_=container, comment=comment, out=out) - except Exception: + except Exception as e: _invalid(status, id_=image, out=traceback.format_exc()) + raise e __salt__['mine.send']('docker.get_containers', host=True) return status diff --git a/salt/modules/gpg.py b/salt/modules/gpg.py index 3c80450a45..29e1d4c8f0 100644 --- a/salt/modules/gpg.py +++ b/salt/modules/gpg.py @@ -776,7 +776,6 @@ def trust_key(keyid=None, salt '*' gpg.trust_key keyid='3FAD9F1E' trust_level='marginally' salt '*' gpg.trust_key fingerprint='53C96788253E58416D20BCD352952C84C3252192' trust_level='not_trusted' salt '*' gpg.trust_key keys=3FAD9F1E trust_level='ultimately' user='username' - ''' ret = { 'res': True, diff --git a/salt/modules/ipmi.py b/salt/modules/ipmi.py index f95726beb5..4f87b78a34 100644 --- a/salt/modules/ipmi.py +++ b/salt/modules/ipmi.py @@ -279,8 +279,11 @@ def get_channel_access(channel=14, read_mode='non_volatile', **kwargs): - api_port=623 - api_kg=None - :return: A Python dict with the following keys/values: + return + A Python dict with the following keys/values: + .. code-block:: python + { alerting: per_msg_auth: @@ -322,14 +325,17 @@ def get_channel_info(channel=14, **kwargs): - api_port=623 - api_kg=None - :return: - session_support: - no_session: channel is session-less - single: channel is single-session - multi: channel is multi-session - auto: channel is session-based (channel could alternate between - single- and multi-session operation, as can occur with a - serial/modem channel that supports connection mode auto-detect) + return + channel session supports: + + .. code-block:: none + + - no_session: channel is session-less + - single: channel is single-session + - multi: channel is multi-session + - auto: channel is session-based (channel could alternate between + single- and multi-session operation, as can occur with a + serial/modem channel that supports connection mode auto-detect) CLI Examples: @@ -419,19 +425,20 @@ def get_user_access(uid, channel=14, **kwargs): - api_port=623 - api_kg=None - :return: - channel_info: - - max_user_count = maximum number of user IDs on this channel - - enabled_users = count of User ID slots presently in use - - users_with_fixed_names = count of user IDs with fixed names - access: - - callback - - link_auth - - ipmi_msg - - privilege_level: [reserved, callback, user, operator - administrator, proprietary, no_access] + return + .. code-block:: none + channel_info: + - max_user_count = maximum number of user IDs on this channel + - enabled_users = count of User ID slots presently in use + - users_with_fixed_names = count of user IDs with fixed names + access: + - callback + - link_auth + - ipmi_msg + - privilege_level: [reserved, callback, user, operator + administrator, proprietary, no_access] CLI Examples: @@ -770,16 +777,20 @@ def get_user(uid, channel=14, **kwargs): - api_port=623 - api_kg=None - :return: - name: (str) - uid: (int) - channel: (int) - access: - - callback (bool) - - link_auth (bool) - - ipmi_msg (bool) - - privilege_level: (str)[callback, user, operatorm administrator, - proprietary, no_access] + return + + .. code-block:: none + + name: (str) + uid: (int) + channel: (int) + access: + - callback (bool) + - link_auth (bool) + - ipmi_msg (bool) + - privilege_level: (str)[callback, user, operatorm administrator, + proprietary, no_access] + CLI Examples: .. code-block:: bash diff --git a/salt/modules/mount.py b/salt/modules/mount.py index 9556a4c90d..4d0447f5cb 100644 --- a/salt/modules/mount.py +++ b/salt/modules/mount.py @@ -696,7 +696,7 @@ def mount(name, device, mkmnt=False, fstype='', opts='defaults', user=None): opts = opts.split(',') if not os.path.exists(name) and mkmnt: - __salt__['file.mkdir'](name=name, user=user) + __salt__['file.mkdir'](name, user=user) args = '' if opts is not None: diff --git a/salt/modules/nacl.py b/salt/modules/nacl.py index 1ed435932b..d7f941ab30 100644 --- a/salt/modules/nacl.py +++ b/salt/modules/nacl.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- ''' -:requires: libnacl -https://github.com/saltstack/libnacl - This module helps include encrypted passwords in pillars, grains and salt state files. -This is often usefull if you wish to store your pillars in source control or -share your pillar data with others that you trust. I dont advise making your pillars public + +:depends: libnacl, https://github.com/saltstack/libnacl + +This is often useful if you wish to store your pillars in source control or +share your pillar data with others that you trust. I don't advise making your pillars public regardless if they are encrypted or not. When generating keys and encrypting passwords use --local when using salt-call for extra @@ -14,20 +14,28 @@ security. Also consider using just the salt runner nacl when encrypting pillar p The nacl lib uses 32byte keys, these keys are base64 encoded to make your life more simple. To generate your `key` or `keyfile` you can use: +.. code-block:: bash + salt-call --local nacl.keygen keyfile=/root/.nacl -Now with your key, you can encrypt some data +Now with your key, you can encrypt some data: + +.. code-block:: bash salt-call --local nacl.enc mypass keyfile=/root/.nacl DRB7Q6/X5gGSRCTpZyxS6hXO5LnlJIIJ4ivbmUlbWj0llUA+uaVyvou3vJ4= -To decrypt the data +To decrypt the data: + +.. code-block:: bash salt-call --local nacl.dec data='DRB7Q6/X5gGSRCTpZyxS6hXO5LnlJIIJ4ivbmUlbWj0llUA+uaVyvou3vJ4=' keyfile=/root/.nacl mypass The following optional configurations can be defined in the -minion or master config. Avoide storeing the config in pillars! +minion or master config. Avoid storing the config in pillars! + +.. code-block:: yaml cat /etc/salt/master.d/nacl.conf nacl.config: @@ -36,15 +44,21 @@ minion or master config. Avoide storeing the config in pillars! When the key is defined in the master config you can use it from the nacl runner: +.. code-block:: bash + salt-run nacl.enc 'myotherpass' Now you can create a pillar with protected data like: +.. code-block:: yaml + pillarexample: user: root password: {{ salt.nacl.dec('DRB7Q6/X5gGSRCTpZyxS6hXO5LnlJIIJ4ivbmUlbWj0llUA+uaVyvou3vJ4=') }} -Or do somthing interesting with grains like: +Or do something interesting with grains like: + +.. code-block:: yaml salt-call nacl.enc minionname:dbrole AL24Z2C5OlkReer3DuQTFdrNLchLuz3NGIhGjZkLtKRYry/b/CksWM8O9yskLwH2AGVLoEXI5jAa diff --git a/salt/modules/npm.py b/salt/modules/npm.py index 27cb04b584..75a72cfd70 100644 --- a/salt/modules/npm.py +++ b/salt/modules/npm.py @@ -119,6 +119,11 @@ def install(pkg=None, elif pkgs: cmd += ' "{0}"'.format('" "'.join(pkgs)) + if runas: + uid = salt.utils.get_uid(runas) + if uid: + env.update({'SUDO_UID': uid, 'SUDO_USER': ''}) + result = __salt__['cmd.run_all'](cmd, python_shell=False, cwd=dir, runas=runas, env=env) if result['retcode'] != 0: @@ -151,7 +156,8 @@ def install(pkg=None, def uninstall(pkg, dir=None, - runas=None): + runas=None, + env=None): ''' Uninstall an NPM package. @@ -167,6 +173,13 @@ def uninstall(pkg, runas The user to run NPM with + env + Environment variables to set when invoking npm. Uses the same ``env`` + format as the :py:func:`cmd.run ` execution + function. + + .. versionadded:: 2015.5.3 + CLI Example: .. code-block:: bash @@ -175,6 +188,11 @@ def uninstall(pkg, ''' + if runas: + uid = salt.utils.get_uid(runas) + if uid: + env.update({'SUDO_UID': uid, 'SUDO_USER': ''}) + cmd = 'npm uninstall' if dir is None: @@ -182,7 +200,7 @@ def uninstall(pkg, cmd += ' "{0}"'.format(pkg) - result = __salt__['cmd.run_all'](cmd, python_shell=False, cwd=dir, runas=runas) + result = __salt__['cmd.run_all'](cmd, python_shell=False, cwd=dir, runas=runas, env=env) if result['retcode'] != 0: log.error(result['stderr']) @@ -227,6 +245,11 @@ def list_(pkg=None, ''' + if runas: + uid = salt.utils.get_uid(runas) + if uid: + env.update({'SUDO_UID': uid, 'SUDO_USER': ''}) + cmd = 'npm list --silent --json' if dir is None: diff --git a/salt/modules/pacman.py b/salt/modules/pacman.py index 8554fe954b..deafd9f5b8 100644 --- a/salt/modules/pacman.py +++ b/salt/modules/pacman.py @@ -128,10 +128,7 @@ def list_upgrades(refresh=False): if refresh: options.append('-y') - cmd = ( - 'pacman {0} | egrep -v ' - r'"^\s|^:"' - ).format(' '.join(options)) + cmd = ('pacman {0}').format(' '.join(options)) call = __salt__['cmd.run_all'](cmd, output_loglevel='trace') @@ -147,7 +144,9 @@ def list_upgrades(refresh=False): else: out = call['stdout'] - for line in out.splitlines(): + output = iter(out.splitlines()) + next(output) # Skip informational output line + for line in output: comps = line.split(' ') if len(comps) < 2: continue diff --git a/salt/modules/puppet.py b/salt/modules/puppet.py index d276169205..ec5a1f20f3 100644 --- a/salt/modules/puppet.py +++ b/salt/modules/puppet.py @@ -228,11 +228,13 @@ def enable(): def disable(message=None): ''' .. versionadded:: 2014.7.0 + Disable the puppet agent message .. versionadded:: 2015.5.2 - disable message to send to puppet + + Disable message to send to puppet CLI Example: @@ -240,7 +242,6 @@ def disable(message=None): salt '*' puppet.disable salt '*' puppet.disable 'disabled for a good reason' - ''' _check_puppet() diff --git a/salt/modules/random_org.py b/salt/modules/random_org.py index ab2b237160..adf49d23c2 100644 --- a/salt/modules/random_org.py +++ b/salt/modules/random_org.py @@ -681,10 +681,9 @@ def generateBlobs(api_key=None, .. code-block:: bash - salt '*' get_intergers number=5 min=1 max=6 - - salt '*' get_intergers number=5 min=1 max=6 + salt '*' get_integers number=5 min=1 max=6 + salt '*' get_integers number=5 min=1 max=6 ''' ret = {'res': True} diff --git a/salt/modules/state.py b/salt/modules/state.py index 403958b9fb..8106934389 100644 --- a/salt/modules/state.py +++ b/salt/modules/state.py @@ -1,6 +1,12 @@ # -*- coding: utf-8 -*- ''' -Control the state system on the minion +Control the state system on the minion. + +State Caching +------------- + +When a highstate is called, the minion automatically caches a copy of the last high data. +If you then run a highstate with cache=True it will use that cached highdata and won't hit the fileserver. ''' # Import python libs diff --git a/salt/modules/system_profiler.py b/salt/modules/system_profiler.py index 4ea46b32fc..555e0cb84e 100644 --- a/salt/modules/system_profiler.py +++ b/salt/modules/system_profiler.py @@ -58,8 +58,7 @@ def _call_system_profiler(datatype): def receipts(): ''' Return the results of a call to - `system_profiler -xml -detail full - SPInstallHistoryDataType` + ``system_profiler -xml -detail full SPInstallHistoryDataType`` as a dictionary. Top-level keys of the dictionary are the names of each set of install receipts, since there can be multiple receipts with the same name. @@ -99,12 +98,11 @@ def receipts(): def applications(): ''' Return the results of a call to - `system_profiler -xml -detail full - SPApplicationsDataType` + ``system_profiler -xml -detail full SPApplicationsDataType`` as a dictionary. Top-level keys of the dictionary are the names of each set of install receipts, since there can be multiple receipts with the same name. - Contents of each key are a list of dicttionaries. + Contents of each key are a list of dictionaries. Note that this can take a long time depending on how many applications are installed on the target Mac. diff --git a/salt/netapi/rest_cherrypy/app.py b/salt/netapi/rest_cherrypy/app.py index 5022364bee..1619d8bee2 100644 --- a/salt/netapi/rest_cherrypy/app.py +++ b/salt/netapi/rest_cherrypy/app.py @@ -7,17 +7,18 @@ A REST API for Salt .. py:currentmodule:: salt.netapi.rest_cherrypy.app -:depends: - CherryPy Python module. Versions 3.2.{2,3,4} are strongly - recommended due to a known `SSL error - `_ - introduced in version 3.2.5. The issue was reportedly resolved with - CherryPy milestone 3.3, but the patch was committed for version 3.6.1. - - salt-api package +:depends: + - CherryPy Python module. Versions 3.2.{2,3,4} are strongly + recommended due to a known `SSL error + `_ + introduced in version 3.2.5. The issue was reportedly resolved with + CherryPy milestone 3.3, but the patch was committed for version 3.6.1. :optdepends: - ws4py Python module for websockets support. :client_libraries: - Java: https://github.com/SUSE/saltstack-netapi-client-java - Python: https://github.com/saltstack/pepper -:configuration: All authentication is done through Salt's :ref:`external auth +:configuration: + All authentication is done through Salt's :ref:`external auth ` system which requires additional configuration not described here. diff --git a/salt/returners/django_return.py b/salt/returners/django_return.py index 06a765ecb0..2c1eaf390a 100644 --- a/salt/returners/django_return.py +++ b/salt/returners/django_return.py @@ -16,7 +16,7 @@ them. An example Django module that registers a function called 'returner_callback' with this module's 'returner' function: - .. code-block:: python +.. code-block:: python import salt.returners.django_return from django.dispatch import receiver diff --git a/salt/runners/fileserver.py b/salt/runners/fileserver.py index e08703dabc..7dfe56b833 100644 --- a/salt/runners/fileserver.py +++ b/salt/runners/fileserver.py @@ -349,7 +349,7 @@ def lock(backend=None, remote=None): .. note:: This will only operate on enabled backends (those configured in - :master_conf:`fileserver_backend`). + :conf_master:`fileserver_backend`). backend Only set the update lock for the specified backend(s). diff --git a/salt/runners/http.py b/salt/runners/http.py index 8a2dbcd588..f67066d97c 100644 --- a/salt/runners/http.py +++ b/salt/runners/http.py @@ -19,7 +19,7 @@ def query(url, output=True, **kwargs): ''' Query a resource, and decode the return data - .. versionadded:: 2015.2 + .. versionadded:: 2015.5.0 CLI Example: @@ -44,7 +44,7 @@ def update_ca_bundle(target=None, source=None, merge_files=None): ''' Update the local CA bundle file from a URL - .. versionadded:: 2015.2 + .. versionadded:: 2015.5.0 CLI Example: diff --git a/salt/states/boto_elb.py b/salt/states/boto_elb.py index e7923d15cb..3f87f8011b 100644 --- a/salt/states/boto_elb.py +++ b/salt/states/boto_elb.py @@ -236,10 +236,10 @@ def present( listeners A list of listener lists; example:: - [ - ['443', 'HTTPS', 'arn:aws:iam::1111111:server-certificate/mycert'], - ['8443', '80', 'HTTPS', 'HTTP', 'arn:aws:iam::1111111:server-certificate/mycert'] - ] + [ + ['443', 'HTTPS', 'arn:aws:iam::1111111:server-certificate/mycert'], + ['8443', '80', 'HTTPS', 'HTTP', 'arn:aws:iam::1111111:server-certificate/mycert'] + ] subnets A list of subnet IDs in your VPC to attach to your LoadBalancer. diff --git a/salt/states/dockerio.py b/salt/states/dockerio.py index c1c861e2bb..9701d38c13 100644 --- a/salt/states/dockerio.py +++ b/salt/states/dockerio.py @@ -69,6 +69,7 @@ Available Functions my_service: docker.running: - container: mysuperdocker + - image: corp/mysuperdocker_img - ports: "5000/tcp": HostIp: "" @@ -210,6 +211,103 @@ def _get_image_name(image, tag): return image +def _parse_volumes(volumes): + ''' + Parse a given volumes state specification for later use in + modules.docker.create_container(). This produces a dict that can be directly + consumed by the Docker API /containers/create. + + Note: this only really exists for backwards-compatibility, and because + modules.dockerio.start() currently takes a binds argument. + + volumes + A structure containing information about the volumes to be included in the + container that will be created, either: + - a bare dictionary + - a list of dictionaries and lists + + .. code-block:: yaml + + # bare dict style + - volumes: + /usr/local/etc/ssl/certs/example.crt: + bind: /etc/ssl/certs/com.example.internal.crt + ro: True + /var/run: + bind: /var/run/host/ + ro: False + + # list of dicts style: + - volumes: + - /usr/local/etc/ssl/certs/example.crt: + bind: /etc/ssl/certs/com.example.internal.crt + ro: True + - /var/run: /var/run/host/ # read-write bound volume + - /var/lib/mysql # un-bound, container-only volume + + note: bind mounts specified like "/etc/timezone:/tmp/host_tz" will fall + through this parser. + + Returns a dict of volume specifications: + + .. code-block:: yaml + + { + 'bindvols': { + '/usr/local/etc/ssl/certs/example.crt': { + 'bind': '/etc/ssl/certs/com.example.internal.crt', + 'ro': True + }, + '/var/run/': { + 'bind': '/var/run/host', + 'ro': False + }, + }, + 'contvols': [ '/var/lib/mysql/' ] + } + + ''' + log.trace("Parsing given volumes dict: " + str(volumes)) + bindvolumes = {} + contvolumes = [] + if isinstance(volumes, dict): + # If volumes as a whole is a dict, then there's no way to specify a non-bound volume + # so we exit early and assume the dict is properly formed. + bindvolumes = volumes + if isinstance(volumes, list): + for vol in volumes: + if isinstance(vol, dict): + for volsource, voldef in vol.items(): + if isinstance(voldef, dict): + target = voldef['bind'] + read_only = voldef.get('ro', False) + else: + target = str(voldef) + read_only = False + source = volsource + else: # isinstance(vol, dict) + if ':' in vol: + volspec = vol.split(':') + source = volspec[0] + target = volspec[1] + read_only = False + try: + if len(volspec) > 2: + read_only = volspec[2] == "ro" + except IndexError: + pass + else: + contvolumes.append(str(vol)) + continue + bindvolumes[source] = { + 'bind': target, + 'ro': read_only + } + result = {'bindvols': bindvolumes, 'contvols': contvolumes} + log.trace("Finished parsing volumes, with result: " + str(result)) + return result + + def mod_watch(name, sfun=None, *args, **kw): if sfun == 'built': # Needs to refresh the image @@ -531,7 +629,7 @@ def installed(name, - a port to map - a mapping of mapping portInHost : PortInContainer volumes - List of volumes + List of volumes (see notes for the running function) For other parameters, see absolutely first the salt.modules.dockerio execution module and the docker-py python bindings for docker @@ -555,7 +653,7 @@ def installed(name, # if container exists but is not started, try to start it if already_exists: return _valid(comment='image {0!r} already exists'.format(name)) - dports, dvolumes, denvironment = {}, [], {} + dports, denvironment = {}, {} if not ports: ports = [] if not volumes: @@ -574,15 +672,13 @@ def installed(name, else: for k in p: dports[str(p)] = {} - for p in volumes: - vals = [] - if not isinstance(p, dict): - vals.append('{0}'.format(p)) - else: - for k in p: - vals.append('{0}:{1}'.format(k, p[k])) - dvolumes.extend(vals) + + parsed_volumes = _parse_volumes(volumes) + bindvolumes = parsed_volumes['bindvols'] + contvolumes = parsed_volumes['contvols'] + kw = dict( + binds=bindvolumes, command=command, hostname=hostname, user=user, @@ -593,7 +689,7 @@ def installed(name, ports=dports, environment=denvironment, dns=dns, - volumes=dvolumes, + volumes=contvolumes, volumes_from=volumes_from, name=name, cpu_shares=cpu_shares, @@ -860,44 +956,47 @@ def running(name, volumes List of volumes to mount or create in the container (like ``-v`` of ``docker run`` command), mapping host directory to container directory. - To create a volume in the container: + + To specify a volume in the container in terse list format: .. code-block:: yaml - volumes: - - "/var/log/service" + - "/var/log/service" # container-only volume + - "/srv/timezone:/etc/timezone" # bound volume + - "/usr/local/etc/passwd:/etc/passwd:ro" # read-only bound volume - For read-write mounting, use the short form (note that the notion of + You can also use the short dictionary form (note that the notion of source:target from docker is preserved): .. code-block:: yaml - volumes: - - /var/log/service: /var/log/service + - /var/log/service: /var/log/service # mandatory read-write implied - Or, to specify read-only mounting, use the extended form: + Or, alternatively, to specify read-only mounting, use the extended form: .. code-block:: yaml - volumes: - /home/user1: bind: /mnt/vol2 - ro: true + ro: True - /var/www: bind: /mnt/vol1 - ro: false + ro: False - Or (mostly for backwards compatibility) a dict style + Or (for backwards compatibility) another dict style: .. code-block:: yaml - volumes: - /home/user1: - bind: /mnt/vol2 - ro: true - /var/www: - bind: /mnt/vol1 - ro: false + /home/user1: + bind: /mnt/vol2 + ro: True + /var/www: + bind: /mnt/vol1 + ro: False volumes_from List of containers to share volumes with @@ -1084,6 +1183,11 @@ def running(name, else: # assume just a port to expose exposeports.append(str(port)) + + parsed_volumes = _parse_volumes(volumes) + bindvolumes = parsed_volumes['bindvols'] + contvolumes = parsed_volumes['contvols'] + if not already_exists: kwargs = dict(command=command, hostname=hostname, @@ -1095,6 +1199,7 @@ def running(name, ports=exposeports, environment=denvironment, dns=dns, + binds=bindvolumes, volumes=contvolumes, name=name, cpu_shares=cpu_shares, diff --git a/salt/states/ipmi.py b/salt/states/ipmi.py index b0a5ca810b..89e7612c01 100644 --- a/salt/states/ipmi.py +++ b/salt/states/ipmi.py @@ -163,8 +163,10 @@ def user_present(name, uid, password, channel=14, callback=False, callback User Restricted to Callback + False = User Privilege Limit is determined by the User Privilege Limit parameter privilege_level, for both callback and non-callback connections. + True = User Privilege Limit is determined by the privilege_level parameter for callback connections, but is restricted to Callback level for non-callback connections. Thus, a user can only initiate diff --git a/salt/states/linux_acl.py b/salt/states/linux_acl.py index 9a01323c29..05823908e4 100644 --- a/salt/states/linux_acl.py +++ b/salt/states/linux_acl.py @@ -4,7 +4,7 @@ Linux File Access Control Lists Ensure a Linux ACL is present - .. code-block:: yaml +.. code-block:: yaml root: acl.present: @@ -15,7 +15,7 @@ Ensure a Linux ACL is present Ensure a Linux ACL does not exist - .. code-block:: yaml +.. code-block:: yaml root: acl.absent: diff --git a/salt/states/module.py b/salt/states/module.py index b9d537bbd7..933a047792 100644 --- a/salt/states/module.py +++ b/salt/states/module.py @@ -228,7 +228,7 @@ def run(name, **kwargs): ret['result'] = False return ret else: - if mret is not None: + if mret is not None or mret is not {}: ret['changes']['ret'] = mret if 'returner' in kwargs: diff --git a/salt/states/powerpath.py b/salt/states/powerpath.py index 12ebd2a195..6d1dacf5f8 100644 --- a/salt/states/powerpath.py +++ b/salt/states/powerpath.py @@ -19,7 +19,7 @@ def license_present(name): on the host. name - The licnese key to ensure is present + The license key to ensure is present ''' ret = {'name': name, 'changes': {}, @@ -61,7 +61,7 @@ def license_absent(name): on the host. name - The licnese key to ensure is absent + The license key to ensure is absent ''' ret = {'name': name, 'changes': {}, diff --git a/salt/states/rabbitmq_vhost.py b/salt/states/rabbitmq_vhost.py index fa963138c6..f08dfe9799 100644 --- a/salt/states/rabbitmq_vhost.py +++ b/salt/states/rabbitmq_vhost.py @@ -81,7 +81,7 @@ def present(name, 'information or see #6961.' ) - if any(user, owner, conf, write, read): + if any((user, owner, conf, write, read)): salt.utils.warn_until( 'Beryllium', 'Passed \'owner\', \'user\', \'conf\', \'write\' or \'read\' ' diff --git a/salt/states/slack.py b/salt/states/slack.py index ff46368dde..0092e97fe1 100644 --- a/salt/states/slack.py +++ b/salt/states/slack.py @@ -69,7 +69,6 @@ def post_message(name, api_key The api key for Slack to use for authentication, if not specified in the configuration options of master or minion. - ''' ret = {'name': name, 'changes': {}, diff --git a/salt/states/win_servermanager.py b/salt/states/win_servermanager.py index b5d446b733..c8dfdbf458 100644 --- a/salt/states/win_servermanager.py +++ b/salt/states/win_servermanager.py @@ -83,15 +83,16 @@ def removed(name): the server is restarted. Example: - Run ``salt MinionName win_servermanager.list_installed`` to get a list of all features installed. Use the top + + Run ``salt MinionName win_servermanager.list_installed`` to get a list of all features installed. Use the top name listed for each feature, not the indented one. Do not use the role or feature names mentioned in the PKGMGR documentation. - .. code-block:: yaml + .. code-block:: yaml - ISWebserverRole: - win_servermanager.removed: - - name: Web-Server + ISWebserverRole: + win_servermanager.removed: + - name: Web-Server ''' ret = {'name': name, 'result': True, diff --git a/salt/utils/args.py b/salt/utils/args.py index 3f227b1c79..c657104608 100644 --- a/salt/utils/args.py +++ b/salt/utils/args.py @@ -111,9 +111,12 @@ def yamlify_arg(arg): import salt.utils.yamlloader as yamlloader original_arg = arg if '#' in arg: - # Don't yamlify this argument or the '#' and everything after - # it will be interpreted as a comment. - return arg + # Only yamlify if it parses into a non-string type, to prevent + # loss of content due to # as comment character + parsed_arg = yamlloader.load(arg, Loader=yamlloader.SaltYamlSafeLoader) + if isinstance(parsed_arg, six.string_types): + return arg + return parsed_arg if arg == 'None': arg = None else: diff --git a/salt/utils/thin.py b/salt/utils/thin.py index 96f84a5f5a..38d22de3eb 100644 --- a/salt/utils/thin.py +++ b/salt/utils/thin.py @@ -31,6 +31,12 @@ try: except ImportError: # Older jinja does not need markupsafe HAS_MARKUPSAFE = False + +try: + import xml + HAS_XML = True +except ImportError: + HAS_XML = False # pylint: enable=import-error,no-name-in-module try: @@ -117,6 +123,10 @@ def gen_thin(cachedir, extra_mods='', overwrite=False, so_mods=''): if HAS_SSL_MATCH_HOSTNAME: tops.append(os.path.dirname(os.path.dirname(ssl_match_hostname.__file__))) + if HAS_XML: + # For openSUSE, which apparently doesn't include the whole stdlib + tops.append(os.path.dirname(xml.__file__)) + for mod in [m for m in extra_mods.split(',') if m]: if mod not in locals() and mod not in globals(): try: diff --git a/salt/wheel/config.py b/salt/wheel/config.py index 77f76fda46..c4976fa45c 100644 --- a/salt/wheel/config.py +++ b/salt/wheel/config.py @@ -52,10 +52,12 @@ def update_config(file_name, yaml_contents): specified by ``default_include``. This folder is named ``master.d`` by default. Please look at - http://docs.saltstack.com/en/latest/ref/configuration/master.html#include-configuration + :conf_master:`include-configuration` for more information. - Example low data:: + Example low data: + + .. code-block:: yaml data = { 'username': 'salt', diff --git a/tests/unit/states/pyrax_queues_test.py b/tests/unit/states/pyrax_queues_test.py new file mode 100644 index 0000000000..145833863d --- /dev/null +++ b/tests/unit/states/pyrax_queues_test.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch +) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import pyrax_queues + +pyrax_queues.__opts__ = {} +pyrax_queues.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class PyraxQueuesTestCase(TestCase): + ''' + Test cases for salt.states.pyrax_queues + ''' + # 'present' function tests: 1 + + def test_present(self): + ''' + Test to ensure the RackSpace queue exists. + ''' + name = 'myqueue' + provider = 'my-pyrax' + + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': ''} + + mock_dct = MagicMock(side_effect=[{provider: {'salt': True}}, + {provider: {'salt': False}}, + {provider: {'salt': False}}, False]) + with patch.dict(pyrax_queues.__salt__, {'cloud.action': mock_dct}): + comt = ('{0} present.'.format(name)) + ret.update({'comment': comt}) + self.assertDictEqual(pyrax_queues.present(name, provider), ret) + + with patch.dict(pyrax_queues.__opts__, {'test': True}): + comt = ('Rackspace queue myqueue is set to be created.') + ret.update({'comment': comt, 'result': None}) + self.assertDictEqual(pyrax_queues.present(name, provider), ret) + + with patch.dict(pyrax_queues.__opts__, {'test': False}): + comt = ('Failed to create myqueue Rackspace queue.') + ret.update({'comment': comt, 'result': False}) + self.assertDictEqual(pyrax_queues.present(name, provider), ret) + + # 'absent' function tests: 1 + + def test_absent(self): + ''' + Test to ensure the named Rackspace queue is deleted. + ''' + name = 'myqueue' + provider = 'my-pyrax' + + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': ''} + + mock_dct = MagicMock(side_effect=[{provider: {'salt': False}}, + {provider: {'salt': True}}]) + with patch.dict(pyrax_queues.__salt__, {'cloud.action': mock_dct}): + comt = ('myqueue does not exist.') + ret.update({'comment': comt}) + self.assertDictEqual(pyrax_queues.absent(name, provider), ret) + + with patch.dict(pyrax_queues.__opts__, {'test': True}): + comt = ('Rackspace queue myqueue is set to be removed.') + ret.update({'comment': comt, 'result': None}) + self.assertDictEqual(pyrax_queues.absent(name, provider), ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(PyraxQueuesTestCase, needs_daemon=False) diff --git a/tests/unit/states/rabbitmq_user_test.py b/tests/unit/states/rabbitmq_user_test.py new file mode 100644 index 0000000000..6d3c417896 --- /dev/null +++ b/tests/unit/states/rabbitmq_user_test.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch +) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import rabbitmq_user + +rabbitmq_user.__opts__ = {} +rabbitmq_user.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class RabbitmqUserTestCase(TestCase): + ''' + Test cases for salt.states.rabbitmq_user + ''' + # 'present' function tests: 1 + + def test_present(self): + ''' + Test to ensure the RabbitMQ user exists. + ''' + name = 'foo' + passwd = 'password' + tag = 'user' + perms = [{'/': ['.*', '.*']}] + + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': ''} + + mock = MagicMock(side_effect=[True, False, True, True, + True, True, True]) + mock_dct = MagicMock(return_value={name: set(tag)}) + mock_pr = MagicMock(return_value=perms) + mock_add = MagicMock(return_value={'Added': name}) + with patch.dict(rabbitmq_user.__salt__, + {'rabbitmq.user_exists': mock, + 'rabbitmq.list_users': mock_dct, + 'rabbitmq.list_user_permissions': mock_pr, + 'rabbitmq.set_user_tags': mock_add}): + comt = ('User foo already presents') + ret.update({'comment': comt}) + self.assertDictEqual(rabbitmq_user.present(name), ret) + + with patch.dict(rabbitmq_user.__opts__, {'test': True}): + comt = ('User foo is set to be created') + ret.update({'comment': comt, 'result': None}) + self.assertDictEqual(rabbitmq_user.present(name), ret) + + comt = ("User foo's password is set to be updated") + ret.update({'comment': comt}) + self.assertDictEqual(rabbitmq_user.present(name, + password=passwd, + force=True), ret) + + comt = ("User foo's password is set to be removed") + ret.update({'comment': comt}) + self.assertDictEqual(rabbitmq_user.present(name, force=True), + ret) + + comt = ('Tags for user foo is set to be changed') + ret.update({'comment': comt}) + self.assertDictEqual(rabbitmq_user.present(name, tags=tag), ret) + + comt = ('Permissions for user foo is set to be changed') + ret.update({'comment': comt}) + self.assertDictEqual(rabbitmq_user.present(name, perms=perms), + ret) + + with patch.dict(rabbitmq_user.__opts__, {'test': False}): + ret.update({'comment': name, 'result': True, + 'changes': {'new': 'Set tags: user\n', 'old': ''}}) + self.assertDictEqual(rabbitmq_user.present(name, tags=tag), ret) + + # 'absent' function tests: 1 + + def test_absent(self): + ''' + Test to ensure the named user is absent. + ''' + name = 'foo' + + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'User {0} is not present'.format(name)} + + mock = MagicMock(return_value=False) + with patch.dict(rabbitmq_user.__salt__, {'rabbitmq.user_exists': mock}): + self.assertDictEqual(rabbitmq_user.absent(name), ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(RabbitmqUserTestCase, needs_daemon=False) diff --git a/tests/unit/states/rabbitmq_vhost_test.py b/tests/unit/states/rabbitmq_vhost_test.py new file mode 100644 index 0000000000..73ca4ca321 --- /dev/null +++ b/tests/unit/states/rabbitmq_vhost_test.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch +) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import rabbitmq_vhost + +rabbitmq_vhost.__opts__ = {} +rabbitmq_vhost.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class RabbitmqVhostTestCase(TestCase): + ''' + Test cases for salt.states.rabbitmq_vhost + ''' + # 'present' function tests: 1 + + def test_present(self): + ''' + Test to ensure the RabbitMQ VHost exists. + ''' + name = 'virtual_host' + + ret = {'name': name, + 'changes': {}, + 'result': None, + 'comment': 'Creating VHost virtual_host'} + + mock = MagicMock(return_value=False) + with patch.dict(rabbitmq_vhost.__salt__, + {'rabbitmq.vhost_exists': mock}): + with patch.dict(rabbitmq_vhost.__opts__, {'test': True}): + self.assertDictEqual(rabbitmq_vhost.present(name), ret) + + # 'absent' function tests: 1 + + def test_absent(self): + ''' + Test to ensure the named user is absent. + ''' + name = 'myqueue' + + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'Virtual Host {0} is not present'.format(name)} + + mock = MagicMock(return_value=False) + with patch.dict(rabbitmq_vhost.__salt__, + {'rabbitmq.vhost_exists': mock}): + self.assertDictEqual(rabbitmq_vhost.absent(name), ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(RabbitmqVhostTestCase, needs_daemon=False) diff --git a/tests/unit/states/rbenv_test.py b/tests/unit/states/rbenv_test.py new file mode 100644 index 0000000000..56ef9714f0 --- /dev/null +++ b/tests/unit/states/rbenv_test.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch +) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import rbenv + +rbenv.__opts__ = {} +rbenv.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class RbenvTestCase(TestCase): + ''' + Test cases for salt.states.rbenv + ''' + # 'installed' function tests: 1 + + def test_installed(self): + ''' + Test to verify that the specified ruby is installed with rbenv. + ''' + name = 'rbenv-deps' + + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': ''} + + mock_t = MagicMock(side_effect=[False, True, True]) + mock_f = MagicMock(return_value=False) + mock_def = MagicMock(return_value='2.7') + mock_ver = MagicMock(return_value=['2.7']) + with patch.dict(rbenv.__salt__, + {'rbenv.is_installed': mock_f, + 'rbenv.install': mock_t, + 'rbenv.default': mock_def, + 'rbenv.versions': mock_ver, + 'rbenv.install_ruby': mock_t}): + with patch.dict(rbenv.__opts__, {'test': True}): + comt = ('Ruby rbenv-deps is set to be installed') + ret.update({'comment': comt, 'result': None}) + self.assertDictEqual(rbenv.installed(name), ret) + + with patch.dict(rbenv.__opts__, {'test': False}): + comt = ('Rbenv failed to install') + ret.update({'comment': comt, 'result': False}) + self.assertDictEqual(rbenv.installed(name), ret) + + comt = ('Successfully installed ruby') + ret.update({'comment': comt, 'result': True, 'default': False, + 'changes': {name: 'Installed'}}) + self.assertDictEqual(rbenv.installed(name), ret) + + # 'absent' function tests: 1 + + def test_absent(self): + ''' + Test to verify that the specified ruby is not installed with rbenv. + ''' + name = 'myqueue' + + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': ''} + + mock = MagicMock(side_effect=[False, True]) + mock_def = MagicMock(return_value='2.7') + mock_ver = MagicMock(return_value=['2.7']) + with patch.dict(rbenv.__salt__, + {'rbenv.is_installed': mock, + 'rbenv.default': mock_def, + 'rbenv.versions': mock_ver}): + with patch.dict(rbenv.__opts__, {'test': True}): + comt = ('Ruby myqueue is set to be uninstalled') + ret.update({'comment': comt, 'result': None}) + self.assertDictEqual(rbenv.absent(name), ret) + + with patch.dict(rbenv.__opts__, {'test': False}): + comt = ('Rbenv not installed, myqueue not either') + ret.update({'comment': comt, 'result': True}) + self.assertDictEqual(rbenv.absent(name), ret) + + comt = ('Ruby myqueue is already absent') + ret.update({'comment': comt, 'result': True}) + self.assertDictEqual(rbenv.absent(name), ret) + + # 'install_rbenv' function tests: 1 + + def test_install_rbenv(self): + ''' + Test to install rbenv if not installed. + ''' + name = 'myqueue' + + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': ''} + + with patch.dict(rbenv.__opts__, {'test': True}): + comt = ('Rbenv is set to be installed') + ret.update({'comment': comt, 'result': None}) + self.assertDictEqual(rbenv.install_rbenv(name), ret) + + with patch.dict(rbenv.__opts__, {'test': False}): + mock = MagicMock(side_effect=[False, True]) + with patch.dict(rbenv.__salt__, + {'rbenv.is_installed': mock, + 'rbenv.install': mock}): + comt = ('Rbenv installed') + ret.update({'comment': comt, 'result': True}) + self.assertDictEqual(rbenv.install_rbenv(name), ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(RbenvTestCase, needs_daemon=False)