diff --git a/doc/ref/cli/spm.rst b/doc/ref/cli/spm.rst index 87db526fa4..de29619813 100644 --- a/doc/ref/cli/spm.rst +++ b/doc/ref/cli/spm.rst @@ -4,7 +4,7 @@ ``spm`` ======= -Salt Package Manager +:ref:`Salt Package Manager ` Synopsis ======== diff --git a/doc/topics/cloud/azure.rst b/doc/topics/cloud/azure.rst index fb5824c677..c6fd2e5f9a 100644 --- a/doc/topics/cloud/azure.rst +++ b/doc/topics/cloud/azure.rst @@ -14,13 +14,25 @@ More information about Azure is located at `http://www.windowsazure.com/ Dependencies ============ -* The `Azure `_ Python SDK >= 0.11.1. +* The `Azure `_ Python SDK >= 0.10.2 and < 1.0.0 * The python-requests library, for Python < 2.7.9. * A Microsoft Azure account * OpenSSL (to generate the certificates) * `Salt `_ +.. note:: + + The Azure driver is currently being updated to work with the new version of + the Python Azure SDK, 1.0.0. However until that process is complete, this + driver will not work with Azure 1.0.0. Please be sure you're running on a + minimum version of 0.10.2 and less than version 1.0.0. + + See `Issue #27980`_ for more information. + +.. _Issue #27980: https://github.com/saltstack/salt/issues/27980 + + Configuration ============= diff --git a/doc/topics/cloud/digitalocean.rst b/doc/topics/cloud/digitalocean.rst index 876f33c044..00348cbd01 100644 --- a/doc/topics/cloud/digitalocean.rst +++ b/doc/topics/cloud/digitalocean.rst @@ -45,14 +45,14 @@ Set up an initial profile at ``/etc/salt/cloud.profiles`` or in the .. code-block:: yaml digitalocean-ubuntu: - provider: my-digitalocean-config - image: 14.04 x64 - size: 512MB - location: New York 1 - private_networking: True - backups_enabled: True - ipv6: True - create_dns_record: True + provider: my-digitalocean-config + image: 14.04 x64 + size: 512MB + location: New York 1 + private_networking: True + backups_enabled: True + ipv6: True + create_dns_record: True Locations can be obtained using the ``--list-locations`` option for the ``salt-cloud`` command: @@ -134,6 +134,28 @@ command: True ...SNIP... + +Profile Specifics: +------------------ + +ssh_username +------------ + +If using a FreeBSD image from Digital Ocean, you'll need to set the ``ssh_username`` +setting to ``freebsd`` in your profile configuration. + +.. code-block:: yaml + + digitalocean-freebsd: + provider: my-digitalocean-config + image: 10.2 + size: 512MB + ssh_username: freebsd + + +Miscellaneous Information +========================= + .. note:: DigitalOcean's concept of ``Applications`` is nothing more than a diff --git a/doc/topics/development/conventions/formulas.rst b/doc/topics/development/conventions/formulas.rst index acdd05ce83..c996f70d2c 100644 --- a/doc/topics/development/conventions/formulas.rst +++ b/doc/topics/development/conventions/formulas.rst @@ -414,13 +414,17 @@ from the Salt Master. For example: {% set some_data = salt.pillar.get('some_data', {'sane default': True}) %} + {# or #} + + {% import_yaml 'path/to/file.yaml' as some_data %} + {# or #} - {% load_json 'path/to/file.json' as some_data %} + {% import_json 'path/to/file.json' as some_data %} {# or #} - {% load_text 'path/to/ssh_key.pub' as ssh_pub_key %} + {% import_text 'path/to/ssh_key.pub' as ssh_pub_key %} {# or #} diff --git a/doc/topics/installation/windows.rst b/doc/topics/installation/windows.rst index 182f66af42..7d1a0c4fbb 100644 --- a/doc/topics/installation/windows.rst +++ b/doc/topics/installation/windows.rst @@ -55,13 +55,18 @@ Silent Installer Options ======================== The installer can be run silently by providing the `/S` option at the command -line. The options `/master` and `/minion-name` allow for configuring the master -hostname and minion name, respectively. Here's an example of using the silent -installer: +line. The installer also accepts the following options for configuring the Salt +Minion silently: + +- `/master=` A string value to set the IP address or host name of the master. Default value is 'salt' +- `/minion-name=` A string value to set the minion name. Default is 'hostname' +- `/start-service=` Either a 1 or 0. '1' will start the service, '0' will not. Default is to start the service after installation. + +Here's an example of using the silent installer: .. code-block:: bat - Salt-Minion-0.17.0-Setup-amd64.exe /S /master=yoursaltmaster /minion-name=yourminionname + Salt-Minion-2015.5.6-Setup-amd64.exe /S /master=yoursaltmaster /minion-name=yourminionname /start-service=0 Running the Salt Minion on Windows as an Unprivileged User diff --git a/doc/topics/releases/2015.8.0.rst b/doc/topics/releases/2015.8.0.rst index f5ba23c237..2ea3a079f7 100644 --- a/doc/topics/releases/2015.8.0.rst +++ b/doc/topics/releases/2015.8.0.rst @@ -39,6 +39,12 @@ SPM (Salt Package Manager) Allows Salt formulas to be packaged for ease of deployment. See :ref:`spm `. +.. note:: + The spm executable was not included in the Debian or Ubuntu packages for the + 2015.8.0 or the 2015.8.1 releases. This executable will be included in an + upcoming release. As a workaround, copy the SPM script from the salt library + installation into ``/usr/local/bin`` or your local equivalent. + Specify a Single Environment for Top Files ========================================== diff --git a/doc/topics/spm/index.rst b/doc/topics/spm/index.rst index 9f7f640c21..2a88b4fdd5 100644 --- a/doc/topics/spm/index.rst +++ b/doc/topics/spm/index.rst @@ -3,7 +3,7 @@ ==================== Salt Package Manager ==================== -The Salt Package Manager, or SPM, allows Salt formulas to be packaged, for ease +The Salt Package Manager, or :ref:`SPM `, allows Salt formulas to be packaged, for ease of deployment. The design of SPM was influenced by other existing packaging systems including RPM, Yum, and Pacman. @@ -60,6 +60,10 @@ For instance, if this version was released in June 2015, the package version should be ``201506``. If multiple releases are made in a month, the ``release`` field should be used. +minimum_version +~~~~~~~~~~~~~~~ +Minimum recommended version of Salt to use this formula. Not currently enforced. + release ~~~~~~~ This field refers primarily to a release of a version, but also to multiple @@ -71,6 +75,7 @@ summary A one-line description of the package. description +~~~~~~~~~~~ A more detailed description of the package which can contain more than one line. Optional Fields @@ -94,7 +99,15 @@ are already treated specially, such as ``pillar.example`` and ``_modules/``. dependencies ~~~~~~~~~~~~ -A list of packages which must be installed before this package can function. +A list of packages which must be installed before this package can function. If +a matching package is found in an SPM repository, the dependency is installed +automatically. + +recommended +~~~~~~~~~~~ +A list of optional packages that are recommended to be installed with the +package. This list is displayed in an informational message +when the package is installed to SPM. Building a Package ------------------ @@ -167,11 +180,11 @@ To install from a repository, use the ``spm install`` command: spm install apache -To install from a local file, use the ``spm local_install`` command: +To install from a local file, use the ``spm local install`` command: .. code-block:: bash - spm local_install /srv/spm/apache-201506-1.spm + spm local install /srv/spm/apache-201506-1.spm Currently, SPM does not check to see if files are already in place before installing them. That means that existing files will be overwritten without @@ -243,8 +256,8 @@ By default, package files are installed using the ``local`` module. This module applies files to the local filesystem, on the machine that the package is installed on. -Please see the SPM Development Guide for information on creating new modules -for package file management. +Please see the :ref:`SPM Development Guide ` for information +on creating new modules for package file management. SPM Configuration diff --git a/doc/topics/targeting/nodegroups.rst b/doc/topics/targeting/nodegroups.rst index 9e7e00b386..84f67a3bcb 100644 --- a/doc/topics/targeting/nodegroups.rst +++ b/doc/topics/targeting/nodegroups.rst @@ -67,3 +67,42 @@ nodegroup`` on the line directly following the nodegroup name. A limited amount of functionality, such as targeting with -N from the command-line may be available without a restart. + +Using Nodegroups in SLS files +============================= + +To use Nodegroups in Jinja logic for SLS files, the :conf_master:`pillar_opts` option in +``/etc/salt/master`` must be set to "True". This will pass the master's configuration as +Pillar data to each minion. + +.. note:: + + If the master's configuration contains any sensitive data, this will be passed to each minion. + Do not enable this option if you have any configuration data that you do not want to get + on your minions. + + Also, if you make changes to your nodegroups, you might need to run + ``salt '*' saltutil.refresh_pillar`` after restarting the master. + +Once pillar_opts is enabled, you can find the nodegroups under the "master" pillar. +To make sure that only the correct minions are targeted, +you should use each matcher for the nodegroup definition. +For example, to check if a minion is in the 'webserver' nodegroup: + +.. code-block:: yaml + + nodegroups: + webserver: 'G@os:Debian and L@minion1,minion2' + +.. code-block:: yaml + + {% if grains.id in salt['pillar.get']('master:nodegroups:webserver', []) + and grains.os in salt['pillar.get']('master:nodegroups:webserver', []) %} + ... + {% endif %} + +.. note:: + + If you do not include all of the matchers used to define a nodegroup, + Salt might incorrectly target minions that meet some of the nodegroup + requirements, but not all of them. diff --git a/doc/topics/tutorials/minionfs.rst b/doc/topics/tutorials/minionfs.rst index a2afa7b4fd..da5e1e0f2b 100644 --- a/doc/topics/tutorials/minionfs.rst +++ b/doc/topics/tutorials/minionfs.rst @@ -4,10 +4,26 @@ MinionFS Backend Walkthrough ============================ +Propagating Files +================= + .. versionadded:: 2014.1.0 -Sometimes, you might need to propagate files that are generated on a minion. -Salt already has a feature to send files from a minion to the master: +Sometimes, one might need to propagate files that are generated on a minion. +Salt already has a feature to send files from a minion to the master. + +Enabling File Propagation +========================= + +To enable propagation, the :conf_master:`file_recv` option needs to be set to ``True``. + +.. code-block:: yaml + + file_recv: True + +These changes require a restart of the master, then new requests for the +``salt://minion-id/`` protocol will send files that are pushed by ``cp.push`` +from ``minion-id`` to the master. .. code-block:: bash @@ -24,6 +40,9 @@ This command will store the file, including its full path, under `. To get up to speed, check out the :doc:`walkthrough `. +MinionFS Backend +================ + Since it is not a good idea to expose the whole :conf_master:`cachedir`, MinionFS should be used to send these files to other minions. @@ -136,4 +155,4 @@ Or we can use a more elegant and salty way to add an SSH key: -.. [*] Yes, that was the actual key on my server, but the server is already destroyed. \ No newline at end of file +.. [*] Yes, that was the actual key on my server, but the server is already destroyed. diff --git a/doc/topics/tutorials/pillar.rst b/doc/topics/tutorials/pillar.rst index 646bfe16d2..20e2bd5905 100644 --- a/doc/topics/tutorials/pillar.rst +++ b/doc/topics/tutorials/pillar.rst @@ -321,6 +321,19 @@ Nested pillar values can also be set via the command line: the key that is passed in will overwrite the entire value of that key, rather than merging only the specified value set via the command line. +The example below will swap the value for vim with telnet in the previously +specified list, notice the nested pillar dict: + +.. code-block:: bash + + salt '*' state.sls edit.vim pillar='{"pkgs": {"vim": "telnet"}}' + +.. note:: + + This will attempt to install telnet on your minions, feel free to + uninstall the package or replace telnet value with anything else. + + More On Pillar ============== diff --git a/doc/topics/tutorials/states_pt2.rst b/doc/topics/tutorials/states_pt2.rst index 10994f3c06..23a1fd8803 100644 --- a/doc/topics/tutorials/states_pt2.rst +++ b/doc/topics/tutorials/states_pt2.rst @@ -31,6 +31,14 @@ You can specify multiple :ref:`state-declaration` under an Try stopping Apache before running ``state.highstate`` once again and observe the output. +.. note:: + + For those running RedhatOS derivatives (Centos, AWS), you will want to specify the + service name to be httpd. More on state service here, :mod:`service state + `. With the example above, just add "- name: httpd" + above the require line and with the same spacing. + + Require other states ==================== diff --git a/pkg/windows/installer/Salt-Minion-Setup.nsi b/pkg/windows/installer/Salt-Minion-Setup.nsi index 25bfbce14d..07864e36e6 100644 --- a/pkg/windows/installer/Salt-Minion-Setup.nsi +++ b/pkg/windows/installer/Salt-Minion-Setup.nsi @@ -6,7 +6,7 @@ !define PRODUCT_UNINST_ROOT_KEY "HKLM" ; MUI 1.67 compatible ------ -!include "MUI.nsh" +!include "MUI2.nsh" !include "nsDialogs.nsh" !include "LogicLib.nsh" @@ -36,6 +36,7 @@ Var MasterHost Var MasterHost_State Var MinionName Var MinionName_State +Var StartService ; MUI Settings !define MUI_ABORTWARNING @@ -54,6 +55,7 @@ Page custom nsDialogsPage nsDialogsPageLeave !insertmacro MUI_PAGE_INSTFILES ; Finish page +!define MUI_PAGE_CUSTOMFUNCTION_SHOW FinishPage.Show !define MUI_FINISHPAGE_RUN "$INSTDIR\nssm" !define MUI_FINISHPAGE_RUN_PARAMETERS "start salt-minion" !insertmacro MUI_PAGE_FINISH @@ -76,6 +78,7 @@ Page custom nsDialogsPage nsDialogsPageLeave Function nsDialogsPage + nsDialogs::Create 1018 Pop $Dialog @@ -110,6 +113,68 @@ Function nsDialogsPageLeave FunctionEnd +Function getMinionConfig + + confFind: + IfFileExists "$INSTDIR\conf\minion" confFound confNotFound + + confNotFound: + ${If} $INSTDIR == "c:\salt\bin\Scripts" + StrCpy $INSTDIR "C:\salt" + goto confFind + ${Else} + goto confReallyNotFound + ${EndIf} + + confFound: + FileOpen $0 "$INSTDIR\conf\minion" r + + confLoop: + FileRead $0 $1 + IfErrors EndOfFile + ${StrLoc} $2 $1 "master:" ">" + ${If} $2 == 0 + ${StrStrAdv} $2 $1 "master: " ">" ">" "0" "0" "0" + ${Trim} $2 $2 + StrCpy $MasterHost_State $2 + ${EndIf} + + ${StrLoc} $2 $1 "id:" ">" + ${If} $2 == 0 + ${StrStrAdv} $2 $1 "id: " ">" ">" "0" "0" "0" + ${Trim} $2 $2 + StrCpy $MinionName_State $2 + ${EndIf} + + Goto confLoop + + EndOfFile: + FileClose $0 + + confReallyNotFound: + Push $R0 + Push $R1 + Push $R2 + ${GetParameters} $R0 + ${GetOptions} $R0 "/master=" $R1 + ${GetOptions} $R0 "/minion-name=" $R2 + ${IfNot} $R1 == "" + StrCpy $MasterHost_State $R1 + ${ElseIf} $MasterHost_State == "" + StrCpy $MasterHost_State "salt" + ${EndIf} + ${IfNot} $R2 == "" + StrCpy $MinionName_State $R2 + ${ElseIf} $MinionName_State == "" + StrCpy $MinionName_State "hostname" + ${EndIf} + Pop $R2 + Pop $R1 + Pop $R0 + +FunctionEnd + + Function updateMinionConfig ClearErrors @@ -181,13 +246,54 @@ Section -Post RMDir /R "$INSTDIR\var\cache\salt" ; removing cache from old version Call updateMinionConfig + + Call checkStartService + SectionEnd +Function FinishPage.Show + + ${IfNot} $StartService == 1 + SendMessage $mui.FinishPage.Run ${BM_SETCHECK} ${BST_UNCHECKED} 0 + ${EndIf} + +FunctionEnd + + +Function checkStartService + + ; Check if the start-service option was passed + Push $R0 + Push $R1 + ${GetParameters} $R0 + ${GetOptions} $R0 "/start-service=" $R1 + ; If start-service was passed something, then set it + ${IfNot} $R1 == "" + StrCpy $StartService $R1 + ; Otherwise default to 1 + ${Else} + StrCpy $StartService 1 + ${EndIf} + Pop $R0 + Pop $R1 + +FunctionEnd + + Function .onInstSuccess -; If the installer is running Silently, start the service - IfSilent 0 +2 - Exec 'net start salt-minion' + ; If the installer is running Silently, start the service + IfSilent silentOption notSilent + + silentOption: + + ; If start-service is 1, then start the service + ${If} $StartService == 1 + Exec 'net start salt-minion' + ${EndIf} + + notSilent: + FunctionEnd @@ -233,62 +339,37 @@ Function .onInit DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}" - confFind: - IfFileExists "$INSTDIR\conf\minion" confFound confNotFound + Call getMinionConfig - confNotFound: + ; Check for existing installation + ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "UninstallString" + StrCmp $R0 "" skipUninstall + + ; Found existing installation, prompt to uninstall + MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "${PRODUCT_NAME} is already installed. $\n$\nClick `OK` to remove the existing installation." /SD IDOK IDOK uninst + Abort + + uninst: + ; Make sure we're in the right directory ${If} $INSTDIR == "c:\salt\bin\Scripts" StrCpy $INSTDIR "C:\salt" - goto confFind - ${Else} - goto confReallyNotFound ${EndIf} - confFound: - FileOpen $0 "$INSTDIR\conf\minion" r + ; Stop and remove the salt-minion service + ExecWait "net stop salt-minion" + ExecWait "sc delete salt-minion" - confLoop: - FileRead $0 $1 - IfErrors EndOfFile - ${StrLoc} $2 $1 "master:" ">" - ${If} $2 == 0 - ${StrStrAdv} $2 $1 "master: " ">" ">" "0" "0" "0" - ${Trim} $2 $2 - StrCpy $MasterHost_State $2 - ${EndIf} + ; Remove salt binaries and batch files + Delete "$INSTDIR\uninst.exe" + Delete "$INSTDIR\nssm.exe" + Delete "$INSTDIR\salt*" + RMDir /r "$INSTDIR\bin" - ${StrLoc} $2 $1 "id:" ">" - ${If} $2 == 0 - ${StrStrAdv} $2 $1 "id: " ">" ">" "0" "0" "0" - ${Trim} $2 $2 - StrCpy $MinionName_State $2 - ${EndIf} + ; Remove registry entries + DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" + DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}" - Goto confLoop - - EndOfFile: - FileClose $0 - - confReallyNotFound: - Push $R0 - Push $R1 - Push $R2 - ${GetParameters} $R0 - ${GetOptions} $R0 "/master=" $R1 - ${GetOptions} $R0 "/minion-name=" $R2 - ${IfNot} $R1 == "" - StrCpy $MasterHost_State $R1 - ${ElseIf} $MasterHost_State == "" - StrCpy $MasterHost_State "salt" - ${EndIf} - ${IfNot} $R2 == "" - StrCpy $MinionName_State $R2 - ${ElseIf} $MinionName_State == "" - StrCpy $MinionName_State "hostname" - ${EndIf} - Pop $R2 - Pop $R1 - Pop $R0 + skipUninstall: FunctionEnd diff --git a/salt/cloud/clouds/digital_ocean.py b/salt/cloud/clouds/digital_ocean.py index b2b3fbd33e..4a69b9db14 100644 --- a/salt/cloud/clouds/digital_ocean.py +++ b/salt/cloud/clouds/digital_ocean.py @@ -46,6 +46,7 @@ from salt.exceptions import ( ) import salt.ext.six as six from salt.ext.six.moves import zip +from salt.ext.six import string_types # Import Third Party Libs try: @@ -206,6 +207,9 @@ def get_image(vm_): vm_image = config.get_cloud_config_value( 'image', vm_, __opts__, search_global=False ) + if not isinstance(vm_image, string_types): + vm_image = str(vm_image) + for image in images: if vm_image in (images[image]['name'], images[image]['slug'], diff --git a/salt/cloud/clouds/linode.py b/salt/cloud/clouds/linode.py index af1d65df01..9e9efd648b 100644 --- a/salt/cloud/clouds/linode.py +++ b/salt/cloud/clouds/linode.py @@ -568,7 +568,7 @@ def create_disk_from_distro(vm_, linode_id, swap_size=None): r''' Creates the disk for the Linode from the distribution. - vm_ + vm\_ The VM profile to create the disk for. linode_id @@ -609,7 +609,7 @@ def create_swap_disk(vm_, linode_id, swap_size=None): r''' Creates the disk for the specified Linode. - vm_ + vm\_ The VM profile to create the swap disk for. linode_id @@ -752,7 +752,7 @@ def get_disk_size(vm_, swap, linode_id): r''' Returns the size of of the root disk in MB. - vm_ + vm\_ The VM to get the disk size for. ''' disk_size = get_linode(kwargs={'linode_id': linode_id})['TOTALHD'] @@ -765,7 +765,7 @@ def get_distribution_id(vm_): r''' Returns the distribution ID for a VM - vm_ + vm\_ The VM to get the distribution ID for ''' distributions = _query('avail', 'distributions')['DATA'] @@ -894,7 +894,7 @@ def get_password(vm_): r''' Return the password to use for a VM. - vm_ + vm\_ The configuration to obtain the password from. ''' return config.get_cloud_config_value( @@ -960,7 +960,7 @@ def get_pub_key(vm_): r''' Return the SSH pubkey. - vm_ + vm\_ The configuration to obtain the public key from. ''' return config.get_cloud_config_value( @@ -972,7 +972,7 @@ def get_swap_size(vm_): r''' Returns the amoutn of swap space to be used in MB. - vm_ + vm\_ The VM profile to obtain the swap size from. ''' return config.get_cloud_config_value( @@ -984,7 +984,7 @@ def get_vm_size(vm_): r''' Returns the VM's size. - vm_ + vm\_ The VM to get the size for. ''' vm_size = config.get_cloud_config_value('size', vm_, __opts__) diff --git a/salt/cloud/clouds/nova.py b/salt/cloud/clouds/nova.py index 830d19cb29..6082d131bb 100644 --- a/salt/cloud/clouds/nova.py +++ b/salt/cloud/clouds/nova.py @@ -645,7 +645,7 @@ def create(vm_): return rackconnectv3 = config.get_cloud_config_value( - 'rackconnectv3', vm_, __opts__, default='False', + 'rackconnectv3', vm_, __opts__, default=False, search_global=False ) @@ -721,21 +721,29 @@ def create(vm_): data.public_ips = access_ip return data + # populate return data with private_ips + # when ssh_interface is set to private_ips and public_ips exist + if not result and ssh_interface(vm_) == 'private_ips': + for private_ip in private: + ignore_ip = ignore_cidr(vm_, private_ip) + if private_ip not in data.private_ips and not ignore_ip: + result.append(private_ip) + if cloudnetwork(vm_) is True: data.public_ips = access_ip return data + if public: + data.public_ips = public + if ssh_interface(vm_) != 'private_ips': + return data + if result: log.debug('result = {0}'.format(result)) data.private_ips = result if ssh_interface(vm_) == 'private_ips': return data - if public: - data.public_ips = public - if ssh_interface(vm_) != 'private_ips': - return data - try: data = salt.utils.cloud.wait_for_ip( __query_node_data, diff --git a/salt/cloud/clouds/saltify.py b/salt/cloud/clouds/saltify.py index f5a9d86f42..374862418b 100644 --- a/salt/cloud/clouds/saltify.py +++ b/salt/cloud/clouds/saltify.py @@ -2,6 +2,7 @@ ''' Saltify Module ============== + The Saltify module is designed to install Salt on a remote machine, virtual or bare metal, using SSH. This module is useful for provisioning machines which are already installed, but not Salted. diff --git a/salt/config/__init__.py b/salt/config/__init__.py index dcced7ea69..2b9c32c4ed 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -2748,6 +2748,8 @@ def apply_minion_config(overrides=None, if overrides: opts.update(overrides) + opts['__cli'] = os.path.basename(sys.argv[0]) + if len(opts['sock_dir']) > len(opts['cachedir']) + 10: opts['sock_dir'] = os.path.join(opts['cachedir'], '.salt-unix') diff --git a/salt/fileclient.py b/salt/fileclient.py index d4c528aa1a..479752c2c7 100644 --- a/salt/fileclient.py +++ b/salt/fileclient.py @@ -28,6 +28,7 @@ import salt.utils.templates import salt.utils.url import salt.utils.gzip_util import salt.utils.http +from salt.utils.locales import sdecode from salt.utils.openstack.swift import SaltSwift # pylint: disable=no-name-in-module,import-error @@ -213,8 +214,7 @@ class Client(object): ret = [] - path = salt.utils.locales.sdecode(path) - path = self._check_proto(path) + path = self._check_proto(sdecode(path)) # We want to make sure files start with this *directory*, use # '/' explicitly because the master (that's generating the # list of files) only runs on POSIX @@ -229,7 +229,6 @@ class Client(object): # go through the list of all files finding ones that are in # the target directory and caching them for fn_ in self.file_list(saltenv): - fn_ = salt.utils.locales.sdecode(fn_) if fn_.strip() and fn_.startswith(path): if salt.utils.check_include_exclude( fn_, include_pat, exclude_pat): @@ -794,12 +793,8 @@ class LocalClient(Client): os.path.join(path, prefix), followlinks=True ): for fname in files: - ret.append( - os.path.relpath( - os.path.join(root, fname), - path - ) - ) + relpath = os.path.relpath(os.path.join(root, fname), path) + ret.append(sdecode(relpath)) return ret def file_list_emptydirs(self, saltenv='base', prefix='', env=None): @@ -826,7 +821,7 @@ class LocalClient(Client): os.path.join(path, prefix), followlinks=True ): if len(dirs) == 0 and len(files) == 0: - ret.append(os.path.relpath(root, path)) + ret.append(sdecode(os.path.relpath(root, path))) return ret def dir_list(self, saltenv='base', prefix='', env=None): @@ -852,7 +847,7 @@ class LocalClient(Client): for root, dirs, files in os.walk( os.path.join(path, prefix), followlinks=True ): - ret.append(os.path.relpath(root, path)) + ret.append(sdecode(os.path.relpath(root, path))) return ret def hash_file(self, path, saltenv='base', env=None): diff --git a/salt/grains/core.py b/salt/grains/core.py index cb0200d6ff..e3a3ef7342 100644 --- a/salt/grains/core.py +++ b/salt/grains/core.py @@ -1537,6 +1537,7 @@ def fqdn_ip6(): def ip_interfaces(): ''' Provide a dict of the connected interfaces and their ip addresses + The addresses will be passed as a list for each interface ''' # Provides: # ip_interfaces @@ -1564,6 +1565,7 @@ def ip_interfaces(): def ip4_interfaces(): ''' Provide a dict of the connected interfaces and their ip4 addresses + The addresses will be passed as a list for each interface ''' # Provides: # ip_interfaces @@ -1588,6 +1590,7 @@ def ip4_interfaces(): def ip6_interfaces(): ''' Provide a dict of the connected interfaces and their ip6 addresses + The addresses will be passed as a list for each interface ''' # Provides: # ip_interfaces diff --git a/salt/grains/fx2.py b/salt/grains/fx2.py new file mode 100644 index 0000000000..d33a380e5a --- /dev/null +++ b/salt/grains/fx2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +''' +Generate baseline proxy minion grains +''' +from __future__ import absolute_import +import salt.utils + +__proxyenabled__ = ['rest_sample'] + +__virtualname__ = 'rest_sample' + + +def __virtual__(): + if not salt.utils.is_proxy(): + return False + else: + return __virtualname__ + + +def kernel(): + return {'kernel': 'proxy'} + + +def os(): + return {'os': 'RestExampleOS'} + + +def location(): + return {'location': 'In this darn virtual machine. Let me out!'} + + +def os_family(): + return {'os_family': 'proxy'} + + +def os_data(): + return {'os_data': 'funkyHttp release 1.0.a.4.g'} diff --git a/salt/grains/junos.py b/salt/grains/junos.py index 173b1e9cea..308f97408f 100644 --- a/salt/grains/junos.py +++ b/salt/grains/junos.py @@ -43,11 +43,10 @@ def defaults(): def facts(): - if 'proxymodule' in __opts__: - if 'junos.facts' in __opts__['proxymodule']: - facts = __opts__['proxymodule']['junos.facts']() - facts['version_info'] = 'override' - return facts + if 'junos.facts' in __proxy__: + facts = __proxy__['junos.facts']() + facts['version_info'] = 'override' + return facts return None diff --git a/salt/loader.py b/salt/loader.py index 342d42b758..d903faab87 100644 --- a/salt/loader.py +++ b/salt/loader.py @@ -581,7 +581,7 @@ def grain_funcs(opts): ) -def grains(opts, force_refresh=False): +def grains(opts, force_refresh=False, proxy=None): ''' Return the functions for the dynamic grains and the values for the static grains. @@ -1095,7 +1095,7 @@ class LazyLoader(salt.utils.lazy.LazyDict): log.error('Module/package collision: {0!r} and {1!r}'.format( fpath, self.file_mapping[f_noext][0] )) - if suffix_order.index(ext) >= suffix_order.index(curr_ext): + if not curr_ext or suffix_order.index(ext) >= suffix_order.index(curr_ext): continue # Next filename # Made it this far - add it diff --git a/salt/minion.py b/salt/minion.py index d5e3c0bdba..4253e11323 100644 --- a/salt/minion.py +++ b/salt/minion.py @@ -1206,6 +1206,13 @@ class Minion(MinionBase): salt.utils.appendproctitle(data['jid']) # this seems awkward at first, but it's a workaround for Windows # multiprocessing communication. + if sys.platform.startswith('win') and \ + opts['multiprocessing'] and \ + not salt.log.is_logging_configured(): + # We have to re-init the logging system for Windows + salt.log.setup_console_logger(log_level=opts.get('log_level', 'info')) + if opts.get('log_file'): + salt.log.setup_logfile_logger(opts['log_file'], opts.get('log_level_logfile', 'info')) if not minion_instance: minion_instance = cls(opts) ret = { diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py index 9eb648be87..7b5f97ff43 100644 --- a/salt/modules/aptpkg.py +++ b/salt/modules/aptpkg.py @@ -298,7 +298,7 @@ def latest_version(*names, **kwargs): return ret # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def version(*names, **kwargs): diff --git a/salt/modules/blockdev.py b/salt/modules/blockdev.py index 92c5cd66a4..9616dfc13f 100644 --- a/salt/modules/blockdev.py +++ b/salt/modules/blockdev.py @@ -14,17 +14,28 @@ import logging # Import salt libs import salt.utils +import salt.utils.decorators as decorators log = logging.getLogger(__name__) +__func_alias__ = { + 'format_': 'format' +} + +__virtualname__ = 'blockdev' + def __virtual__(): ''' - Only work on POSIX-like systems + Only load this module if the blockdev utility is available ''' if salt.utils.is_windows(): - return False - return True + return (False, ('The {0} execution module ' + 'is not supported on windows'.format(__virtualname__))) + elif not salt.utils.which('blockdev'): + return (False, ('Cannot load the {0} execution module: ' + 'blockdev utility not found'.format(__virtualname__))) + return __virtualname__ def tune(device, **kwargs): @@ -38,7 +49,7 @@ def tune(device, **kwargs): .. code-block:: bash - salt '*' blockdev.tune /dev/sda1 read-ahead=1024 read-write=True + salt '*' blockdev.tune /dev/sdX1 read-ahead=1024 read-write=True Valid options are: ``read-ahead``, ``filesystem-read-ahead``, ``read-only``, ``read-write``. @@ -53,6 +64,7 @@ def tune(device, **kwargs): return __salt__['disk.tune'](device, **kwargs) +@decorators.which('wipefs') def wipe(device): ''' Remove the filesystem information @@ -64,7 +76,7 @@ def wipe(device): .. code-block:: bash - salt '*' blockdev.wipe /dev/sda1 + salt '*' blockdev.wipe /dev/sdX1 ''' salt.utils.warn_until( 'Carbon', @@ -80,10 +92,14 @@ def dump(device, args=None): .. deprecated:: Boron Use `disk.dump` + args + a list containing only the desired arguments to return + CLI Example: + .. code-block:: bash - salt '*' extfs.dump /dev/sda1 + salt '*' blockdev.dump /dev/sdX1 ''' salt.utils.warn_until( 'Carbon', @@ -92,6 +108,94 @@ def dump(device, args=None): return __salt__['disk.dump'](device, args) +@decorators.which('sync') +@decorators.which('mkfs') +def format_(device, fs_type='ext4', inode_size=None, lazy_itable_init=None): + ''' + Format a filesystem onto a block device + + .. versionadded:: 2015.8.2 + + device + The block device in which to create the new filesystem + + fs_type + The type of filesystem to create + + inode_size + Size of the inodes + + This option is only enabled for ext and xfs filesystems + + lazy_itable_init + If enabled and the uninit_bg feature is enabled, the inode table will + not be fully initialized by mke2fs. This speeds up filesystem + initialization noticeably, but it requires the kernel to finish + initializing the filesystem in the background when the filesystem + is first mounted. If the option value is omitted, it defaults to 1 to + enable lazy inode table zeroing. + + This option is only enabled for ext filesystems + + CLI Example: + + .. code-block:: bash + + salt '*' blockdev.format /dev/sdX1 + ''' + cmd = ['mkfs', '-t', str(fs_type)] + if inode_size is not None: + if fs_type[:3] == 'ext': + cmd.extend(['-i', str(inode_size)]) + elif fs_type == 'xfs': + cmd.extend(['-i', 'size={0}'.format(inode_size)]) + if lazy_itable_init is not None: + if fs_type[:3] == 'ext': + cmd.extend(['-E', 'lazy_itable_init={0}'.format(lazy_itable_init)]) + cmd.append(str(device)) + + mkfs_success = __salt__['cmd.retcode'](cmd, ignore_retcode=True) == 0 + sync_success = __salt__['cmd.retcode']('sync', ignore_retcode=True) == 0 + + return all([mkfs_success, sync_success]) + + +@decorators.which_bin(['lsblk', 'df']) +def fstype(device): + ''' + Return the filesystem name of a block device + + .. versionadded:: 2015.8.2 + + device + The name of the block device + + CLI Example: + + .. code-block:: bash + + salt '*' blockdev.fstype /dev/sdX1 + ''' + if salt.utils.which('lsblk'): + lsblk_out = __salt__['cmd.run']('lsblk -o fstype {0}'.format(device)).splitlines() + if len(lsblk_out) > 1: + fs_type = lsblk_out[1].strip() + if fs_type: + return fs_type + + if salt.utils.which('df'): + # the fstype was not set on the block device, so inspect the filesystem + # itself for its type + df_out = __salt__['cmd.run']('df -T {0}'.format(device)).splitlines() + if len(df_out) > 1: + fs_type = df_out[1] + if fs_type: + return fs_type + + return '' + + +@decorators.which('resize2fs') def resize2fs(device): ''' Resizes the filesystem. @@ -102,7 +206,7 @@ def resize2fs(device): CLI Example: .. code-block:: bash - salt '*' blockdev.resize2fs /dev/sda1 + salt '*' blockdev.resize2fs /dev/sdX1 ''' salt.utils.warn_until( 'Carbon', diff --git a/salt/modules/boto_elasticache.py b/salt/modules/boto_elasticache.py index f5d49c3798..249048f188 100644 --- a/salt/modules/boto_elasticache.py +++ b/salt/modules/boto_elasticache.py @@ -338,7 +338,7 @@ def subnet_group_exists(name, tags=None, region=None, key=None, keyid=None, prof salt myminion boto_elasticache.subnet_group_exists my-param-group \ region=us-east-1 ''' - conn = _get_conn(region, key, keyid, profile) + conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return False try: @@ -364,7 +364,7 @@ def create_subnet_group(name, description, subnet_ids, tags=None, region=None, "group description" '[subnet-12345678, subnet-87654321]' \ region=us-east-1 ''' - conn = _get_conn(region, key, keyid, profile) + conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return False if subnet_group_exists(name, tags, region, key, keyid, profile): @@ -438,7 +438,7 @@ def delete_subnet_group(name, region=None, key=None, keyid=None, profile=None): salt myminion boto_elasticache.delete_subnet_group my-subnet-group \ region=us-east-1 ''' - conn = _get_conn(region, key, keyid, profile) + conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) if not conn: return False try: diff --git a/salt/modules/brew.py b/salt/modules/brew.py index 445ad0a3f8..fbed3920ad 100644 --- a/salt/modules/brew.py +++ b/salt/modules/brew.py @@ -164,7 +164,7 @@ def latest_version(*names, **kwargs): return ret # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def remove(name=None, pkgs=None, **kwargs): diff --git a/salt/modules/bsd_shadow.py b/salt/modules/bsd_shadow.py index 0b7082fc65..664d169325 100644 --- a/salt/modules/bsd_shadow.py +++ b/salt/modules/bsd_shadow.py @@ -146,7 +146,7 @@ def set_expire(name, expire): def del_password(name): ''' - .. versionadded:: 2015.8.3 + .. versionadded:: 2015.8.2 Delete the password from name user diff --git a/salt/modules/chocolatey.py b/salt/modules/chocolatey.py index fc489bdd9c..9663855aeb 100644 --- a/salt/modules/chocolatey.py +++ b/salt/modules/chocolatey.py @@ -102,20 +102,34 @@ def chocolatey_version(): ''' if 'chocolatey._version' in __context__: return __context__['chocolatey._version'] - cmd = [_find_chocolatey(__context__, __salt__)] - out = __salt__['cmd.run'](cmd, python_shell=False) - for line in out.splitlines(): - line = line.lower() - if line.startswith('chocolatey v'): - __context__['chocolatey._version'] = line[12:] - return __context__['chocolatey._version'] - elif line.startswith('version: '): - try: - __context__['chocolatey._version'] = \ - line.split(None, 1)[-1].strip("'") + + def find_version(legacy=False): + cmd = [_find_chocolatey(__context__, __salt__)] + if legacy: + cmd.append('help') + out = __salt__['cmd.run'](cmd, python_shell=False) + for line in out.splitlines(): + line = line.lower() + if line.startswith('chocolatey v'): + __context__['chocolatey._version'] = line[12:] return __context__['chocolatey._version'] - except Exception: - pass + elif line.startswith('version: '): + try: + __context__['chocolatey._version'] = \ + line.split(None, 1)[-1].strip("'") + return __context__['chocolatey._version'] + except Exception: + pass + return None + + # First try to find if we have a newer version of choco + # which doesn't contain the help command, + # else try for a legacy version + for legacy in [False, True]: + ver = find_version(legacy=legacy) + if ver is not None: + return ver + raise CommandExecutionError('Unable to determine Chocolatey version') diff --git a/salt/modules/cron.py b/salt/modules/cron.py index 72a430e62d..54e9c4a023 100644 --- a/salt/modules/cron.py +++ b/salt/modules/cron.py @@ -316,7 +316,7 @@ def list_tab(user): return ret # For consistency's sake -ls = list_tab # pylint: disable=C0103 +ls = salt.utils.alias_function(list_tab, 'ls') def set_special(user, special, cmd): @@ -526,7 +526,7 @@ def rm_job(user, return comdat['stderr'] return ret -rm = rm_job # pylint: disable=C0103 +rm = salt.utils.alias_function(rm_job, 'rm') def set_env(user, name, value=None): diff --git a/salt/modules/debian_ip.py b/salt/modules/debian_ip.py index c6de860ee9..51627978ed 100644 --- a/salt/modules/debian_ip.py +++ b/salt/modules/debian_ip.py @@ -882,9 +882,9 @@ def _parse_settings_bond_1(opts, iface, bond_def): if 'use_carrier' in opts: if opts['use_carrier'] in _CONFIG_TRUE: - bond.update({'use_carrier': 'on'}) + bond.update({'use_carrier': '1'}) elif opts['use_carrier'] in _CONFIG_FALSE: - bond.update({'use_carrier': 'off'}) + bond.update({'use_carrier': '0'}) else: valid = _CONFIG_TRUE + _CONFIG_FALSE _raise_error_iface(iface, 'use_carrier', valid) @@ -968,9 +968,9 @@ def _parse_settings_bond_3(opts, iface, bond_def): if 'use_carrier' in opts: if opts['use_carrier'] in _CONFIG_TRUE: - bond.update({'use_carrier': 'on'}) + bond.update({'use_carrier': '1'}) elif opts['use_carrier'] in _CONFIG_FALSE: - bond.update({'use_carrier': 'off'}) + bond.update({'use_carrier': '0'}) else: valid = _CONFIG_TRUE + _CONFIG_FALSE _raise_error_iface(iface, 'use_carrier', valid) @@ -1012,9 +1012,9 @@ def _parse_settings_bond_4(opts, iface, bond_def): if 'use_carrier' in opts: if opts['use_carrier'] in _CONFIG_TRUE: - bond.update({'use_carrier': 'on'}) + bond.update({'use_carrier': '1'}) elif opts['use_carrier'] in _CONFIG_FALSE: - bond.update({'use_carrier': 'off'}) + bond.update({'use_carrier': '0'}) else: valid = _CONFIG_TRUE + _CONFIG_FALSE _raise_error_iface(iface, 'use_carrier', valid) @@ -1055,9 +1055,9 @@ def _parse_settings_bond_5(opts, iface, bond_def): if 'use_carrier' in opts: if opts['use_carrier'] in _CONFIG_TRUE: - bond.update({'use_carrier': 'on'}) + bond.update({'use_carrier': '1'}) elif opts['use_carrier'] in _CONFIG_FALSE: - bond.update({'use_carrier': 'off'}) + bond.update({'use_carrier': '0'}) else: valid = _CONFIG_TRUE + _CONFIG_FALSE _raise_error_iface(iface, 'use_carrier', valid) @@ -1091,9 +1091,9 @@ def _parse_settings_bond_6(opts, iface, bond_def): if 'use_carrier' in opts: if opts['use_carrier'] in _CONFIG_TRUE: - bond.update({'use_carrier': 'on'}) + bond.update({'use_carrier': '1'}) elif opts['use_carrier'] in _CONFIG_FALSE: - bond.update({'use_carrier': 'off'}) + bond.update({'use_carrier': '0'}) else: valid = _CONFIG_TRUE + _CONFIG_FALSE _raise_error_iface(iface, 'use_carrier', valid) diff --git a/salt/modules/dockerng.py b/salt/modules/dockerng.py index de38f2203f..975c9b024e 100644 --- a/salt/modules/dockerng.py +++ b/salt/modules/dockerng.py @@ -3018,7 +3018,7 @@ def copy_from(name, source, dest, overwrite=False, makedirs=False): # Docker cp gets a file from the container, alias this to copy_from -cp = copy_from +cp = salt.utils.alias_function(copy_from, 'cp') @_ensure_exists @@ -4302,7 +4302,7 @@ def pause(name): .format(name))} return _change_state(name, 'pause', 'paused') -freeze = pause +freeze = salt.utils.alias_function(pause, 'freeze') @_ensure_exists @@ -4497,7 +4497,7 @@ def unpause(name): .format(name))} return _change_state(name, 'unpause', 'running') -unfreeze = unpause +unfreeze = salt.utils.alias_function(unpause, 'unfreeze') def wait(name): diff --git a/salt/modules/dracr.py b/salt/modules/dracr.py index 69664d0558..8b522cdb1a 100644 --- a/salt/modules/dracr.py +++ b/salt/modules/dracr.py @@ -42,10 +42,10 @@ def __parse_drac(output): drac[section].update(dict( [[prop.strip() for prop in i.split('=')]] )) - else: - section = i.strip()[:-1] - if section not in drac and section: - drac[section] = {} + else: + section = i.strip()[1:-1] + if section not in drac and section: + drac[section] = {} return drac @@ -125,11 +125,37 @@ def __execute_ret(command, host=None, if len(l.strip()) == 0: continue fmtlines.append(l) + if '=' in l: + continue + break cmd['stdout'] = '\n'.join(fmtlines) return cmd +def get_dns_dracname(host=None, + admin_username=None, admin_password=None): + import pydevd + pydevd.settrace('172.16.207.1', port=65500, stdoutToServer=True, stderrToServer=True) + + ret = __execute_ret('get iDRAC.NIC.DNSRacName', host=host, + admin_username=admin_username, + admin_password=admin_password) + parsed = __parse_drac(ret['stdout']) + return parsed + + +def set_dns_dracname(name, + host=None, + admin_username=None, admin_password=None): + + ret = __execute_ret('set iDRAC.NIC.DNSRacName {0}'.format(name), + host=host, + admin_username=admin_username, + admin_password=admin_password) + return ret + + def system_info(host=None, admin_username=None, admin_password=None, module=None): @@ -645,6 +671,41 @@ def set_network(ip, netmask, gateway, host=None, )) +def server_power(status, host=None, + admin_username=None, + admin_password=None, + module=None): + ''' + status + One of 'powerup', 'powerdown', 'powercycle', 'hardreset', + 'graceshutdown' + + host + The chassis host. + + admin_username + The username used to access the chassis. + + admin_password + The password used to access the chassis. + + module + The element to reboot on the chassis such as a blade. If not provided, + the chassis will be rebooted. + + CLI Example: + + .. code-block:: bash + + salt dell dracr.server_reboot + salt dell dracr.server_reboot module=server-1 + + ''' + return __execute_cmd('serveraction {0}'.format(status), + host=host, admin_username=admin_username, + admin_password=admin_password, module=module) + + def server_reboot(host=None, admin_username=None, admin_password=None, @@ -946,10 +1007,9 @@ def set_slotname(slot, name, host=None, admin_username=root admin_password=secret ''' - return __execute_cmd('setslotname -i {0} {1}'.format( - slot, name[0:14], host=host, - admin_username=admin_username, - admin_password=admin_password)) + return __execute_cmd('config -g cfgServerInfo -o cfgServerName -i {0} {1}'.format(slot, name), + host=host, admin_username=admin_username, + admin_password=admin_password) def set_chassis_name(name, @@ -1147,8 +1207,67 @@ def get_chassis_location(host=None, ''' return system_info(host=host, - admin_username=admin_username, - admin_password=admin_password)['Chassis Information']['Chassis Location'] + admin_username=admin_username, + admin_password=admin_password)['Chassis Information']['Chassis Location'] + + +def set_chassis_datacenter(location, + host=None, + admin_username=None, + admin_password=None): + ''' + Set the location of the chassis. + + location + The name of the datacenter to be set on the chassis. + + host + The chassis host. + + admin_username + The username used to access the chassis. + + admin_password + The password used to access the chassis. + + CLI Example: + + .. code-block:: bash + + salt '*' dracr.set_chassis_datacenter datacenter-name host=111.222.333.444 + admin_username=root admin_password=secret + + ''' + return set_general('cfgLocation', 'cfgLocationDatacenter', location, + host=host, admin_username=admin_username, + admin_password=admin_password) + + +def get_chassis_datacenter(host=None, + admin_username=None, + admin_password=None): + ''' + Get the datacenter of the chassis. + + host + The chassis host. + + admin_username + The username used to access the chassis. + + admin_password + The password used to access the chassis. + + CLI Example: + + .. code-block:: bash + + salt '*' dracr.set_chassis_location host=111.222.333.444 + admin_username=root admin_password=secret + + ''' + return get_general('cfgLocation', 'cfgLocationDatacenter', host=host, + admin_username=admin_username, admin_password=admin_password) def set_general(cfg_sec, cfg_var, val, host=None, diff --git a/salt/modules/ebuild.py b/salt/modules/ebuild.py index 4444629cc2..0311179ede 100644 --- a/salt/modules/ebuild.py +++ b/salt/modules/ebuild.py @@ -237,7 +237,7 @@ def latest_version(*names, **kwargs): return ret # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def _get_upgradable(backtrack=3): diff --git a/salt/modules/event.py b/salt/modules/event.py index d1c76f0c0b..a06d4de5fe 100644 --- a/salt/modules/event.py +++ b/salt/modules/event.py @@ -51,16 +51,23 @@ def fire_master(data, tag, preload=None): pass return True - if preload: + if preload or __opts__.get('__cli') == 'salt-call': # If preload is specified, we must send a raw event (this is # slower because it has to independently authenticate) - load = preload + if 'master_uri' not in __opts__: + __opts__['master_uri'] = 'tcp://{ip}:{port}'.format( + ip=salt.utils.ip_bracket(__opts__['interface']), + port=__opts__.get('ret_port', '4506') # TODO, no fallback + ) auth = salt.crypt.SAuth(__opts__) - load.update({'id': __opts__['id'], + load = {'id': __opts__['id'], 'tag': tag, 'data': data, 'tok': auth.gen_token('salt'), - 'cmd': '_minion_event'}) + 'cmd': '_minion_event'} + + if isinstance(preload, dict): + load.update(preload) channel = salt.transport.Channel.factory(__opts__) try: diff --git a/salt/modules/file.py b/salt/modules/file.py index a0ca8724e1..2ea0a5b886 100644 --- a/salt/modules/file.py +++ b/salt/modules/file.py @@ -30,6 +30,7 @@ import tempfile import time import glob import hashlib +import mmap from functools import reduce # pylint: disable=redefined-builtin from collections import Iterable, Mapping @@ -1636,7 +1637,7 @@ def replace(path, pattern, repl, count=0, - flags=0, + flags=8, bufsize=1, append_if_not_found=False, prepend_if_not_found=False, @@ -1670,14 +1671,14 @@ def replace(path, A list of flags defined in the :ref:`re module documentation `. Each list item should be a string that will correlate to the human-friendly flag name. E.g., ``['IGNORECASE', - 'MULTILINE']``. Note: multiline searches must specify ``file`` as the - ``bufsize`` argument below. + 'MULTILINE']``. Optionally, ``flags`` may be an int, with a value + corresponding to the XOR (``|``) of all the desired flags. Defaults to + 8 (which supports 'MULTILINE'). bufsize (int or str) How much of the file to buffer into memory at once. The default value ``1`` processes one line at a time. The special value ``file`` may be specified which will read the entire file into memory - before processing. Note: multiline searches must specify ``file`` - buffering. + before processing. append_if_not_found .. versionadded:: 2014.7.0 @@ -1778,8 +1779,9 @@ def replace(path, flags_num = _get_flags(flags) cpattern = re.compile(str(pattern), flags_num) + filesize = os.path.getsize(path) if bufsize == 'file': - bufsize = os.path.getsize(path) + bufsize = filesize # Search the file; track if any changes have been made for the return val has_changes = False @@ -1800,60 +1802,57 @@ def replace(path, append_if_not_found) \ else repl - if search_only: + # mmap throws a ValueError if the file is empty, but if it is empty we + # should be able to skip the search anyway. NOTE: Is there a use case for + # searching an empty file with an empty pattern? + if filesize is not 0: + # First check the whole file, determine whether to make the replacement + # Searching first avoids modifying the time stamp if there are no changes try: - # allow multiline searching - with salt.utils.filebuffer.BufferedReader(path) as breader: - for chunk in breader: - if re.search(cpattern, chunk): - return True - return False + # Use a read-only handle to open the file + with salt.utils.fopen(path, + mode='rb', + buffering=bufsize) as r_file: + r_data = mmap.mmap(r_file.fileno(), + 0, + access=mmap.ACCESS_READ) + if search_only: + # Just search; bail as early as a match is found + if re.search(cpattern, r_data): + return True # `with` block handles file closure + else: + result, nrepl = re.subn(cpattern, repl, r_data, count) + + # found anything? (even if no change) + if nrepl > 0: + found = True + # Identity check the potential change + has_changes = True if pattern != repl else has_changes + + if prepend_if_not_found or append_if_not_found: + # Search for content, to avoid pre/appending the + # content if it was pre/appended in a previous run. + if re.search('^{0}$'.format(re.escape(content)), + r_data, + flags=flags_num): + # Content was found, so set found. + found = True + + # Keep track of show_changes here, in case the file isn't + # modified + if show_changes or append_if_not_found or \ + prepend_if_not_found: + orig_file = r_data.read(filesize).splitlines(True) + new_file = result.splitlines(True) + except (OSError, IOError) as exc: raise CommandExecutionError( - "Unable to read file '{0}'. Exception: {1}".format(path, exc) + "Unable to open file '{0}'. " + "Exception: {1}".format(path, exc) ) - - # First check the whole file, determine whether to make the replacement - # Searching first avoids modifying the time stamp if there are no changes - try: - # Use a read-only handle to open the file - with salt.utils.fopen(path, - mode='r', - buffering=bufsize) as r_file: - count_replaced = 0 - for line in r_file: - if count == 0 or count_replaced <= count: - result, nrepl = re.subn(cpattern, repl, line, count) - count_replaced += nrepl - else: - break - - # found anything? (even if no change) - if nrepl > 0: - found = True - - if prepend_if_not_found or append_if_not_found: - # Search for content, so we don't continue pre/appending - # the content if it's been pre/appended in a previous run. - if re.search('^{0}$'.format(re.escape(content)), line): - # Content was found, so set found. - found = True - - # Identity check each potential change until one change is made - if has_changes is False and result != line: - has_changes = True - - # Keep track of show_changes here, in case the file isn't - # modified - if show_changes or append_if_not_found or \ - prepend_if_not_found: - orig_file.append(line) - new_file.append(result) - - except (OSError, IOError) as exc: - raise CommandExecutionError( - "Unable to open file '{0}'. Exception: {1}".format(path, exc) - ) + finally: + if r_data and isinstance(r_data, mmap.mmap): + r_data.close() if has_changes and not dry_run: # Write the replacement text in this block. @@ -1874,26 +1873,25 @@ def replace(path, with salt.utils.fopen(temp_file, mode='r', buffering=bufsize) as r_file: - count_replaced = 0 - for line in r_file: - result, nrepl = re.subn(cpattern, repl, - line, count) - if count == 0 or count_replaced <= count: - result, nrepl = re.subn(cpattern, repl, line, count) - count_replaced += nrepl - else: - result = line - try: - w_file.write(result) - except (OSError, IOError) as exc: - raise CommandExecutionError( - "Unable to write file '{0}'. Contents may " - "be truncated. Temporary file contains copy " - "at '{1}'. " - "Exception: {2}".format(path, temp_file, exc) - ) + r_data = mmap.mmap(r_file.fileno(), + 0, + access=mmap.ACCESS_READ) + result, nrepl = re.subn(cpattern, repl, + r_data, count) + try: + w_file.write(result) + except (OSError, IOError) as exc: + raise CommandExecutionError( + "Unable to write file '{0}'. Contents may " + "be truncated. Temporary file contains copy " + "at '{1}'. " + "Exception: {2}".format(path, temp_file, exc) + ) except (OSError, IOError) as exc: raise CommandExecutionError("Exception: {0}".format(exc)) + finally: + if r_data and isinstance(r_data, mmap.mmap): + r_data.close() except (OSError, IOError) as exc: raise CommandExecutionError("Exception: {0}".format(exc)) @@ -2178,7 +2176,7 @@ def blockreplace(path, def search(path, pattern, - flags=0, + flags=8, bufsize=1, ignore_if_missing=False, multiline=False @@ -4720,7 +4718,7 @@ def list_backups(path, limit=None): [files[x] for x in sorted(files, reverse=True)[:limit]] ))) -list_backup = list_backups +list_backup = salt.utils.alias_function(list_backups, 'list_backup') def list_backups_dir(path, limit=None): @@ -4892,7 +4890,7 @@ def delete_backup(path, backup_id): return ret -remove_backup = delete_backup +remove_backup = salt.utils.alias_function(delete_backup, 'remove_backup') def grep(path, diff --git a/salt/modules/firewalld.py b/salt/modules/firewalld.py index 58539a54a8..41dcec242c 100644 --- a/salt/modules/firewalld.py +++ b/salt/modules/firewalld.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - ''' Support for firewalld. @@ -12,6 +11,7 @@ import logging import re # Import Salt Libs +from salt.exceptions import CommandExecutionError import salt.utils log = logging.getLogger(__name__) @@ -31,16 +31,18 @@ def __firewall_cmd(cmd): ''' Return the firewall-cmd location ''' - out = __salt__['cmd.run']('{0} {1}'.format( - salt.utils.which('firewall-cmd'), - cmd)) + firewall_cmd = '{0} {1}'.format(salt.utils.which('firewall-cmd'), cmd) + out = __salt__['cmd.run_all'](firewall_cmd) - if out == 'success': - return 'success' - elif 'Error' in out: - return out[5:-5] - - return out + if out['retcode'] != 0: + if not out['stderr']: + msg = out['stdout'] + else: + msg = out['stderr'] + raise CommandExecutionError( + 'firewall-cmd failed: {0}'.format(msg) + ) + return out['stdout'] def __mgmt(name, _type, action): diff --git a/salt/modules/freebsdpkg.py b/salt/modules/freebsdpkg.py index b5cc242eed..3fc9c9f6a6 100644 --- a/salt/modules/freebsdpkg.py +++ b/salt/modules/freebsdpkg.py @@ -192,7 +192,7 @@ def latest_version(*names, **kwargs): return '' if len(names) == 1 else dict((x, '') for x in names) # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def version(*names, **kwargs): @@ -463,9 +463,9 @@ def remove(name=None, pkgs=None, **kwargs): return salt.utils.compare_dicts(old, new) # Support pkg.delete to remove packages to more closely match pkg_delete -delete = remove +delete = salt.utils.alias_function(remove, 'delete') # No equivalent to purge packages, use remove instead -purge = remove +purge = salt.utils.alias_function(remove, 'purge') def _rehash(): diff --git a/salt/modules/git.py b/salt/modules/git.py index 3c1b2e9487..b7418ff884 100644 --- a/salt/modules/git.py +++ b/salt/modules/git.py @@ -978,7 +978,7 @@ def config_get_regexp(key, ret.setdefault(param, []).append(value) return ret -config_get_regex = config_get_regexp +config_get_regex = salt.utils.alias_function(config_get_regexp, 'config_get_regex') def config_set(key, diff --git a/salt/modules/img.py b/salt/modules/img.py index 293df14018..9e65660736 100644 --- a/salt/modules/img.py +++ b/salt/modules/img.py @@ -7,6 +7,9 @@ Virtual machine image management tools from __future__ import absolute_import import logging +# Import salt libs +import salt.utils + # Import 3rd-party libs import salt.ext.six as six @@ -36,7 +39,7 @@ def mount_image(location): return '' # compatibility for api change -mnt_image = mount_image +mnt_image = salt.utils.alias_function(mount_image, 'mnt_image') def umount_image(mnt): diff --git a/salt/modules/incron.py b/salt/modules/incron.py index 3f13cd04ce..e589c21317 100644 --- a/salt/modules/incron.py +++ b/salt/modules/incron.py @@ -211,7 +211,7 @@ def list_tab(user): return ret # For consistency's sake -ls = list_tab # pylint: disable=C0103 +ls = salt.utils.alias_function(list_tab, 'ls') def set_job(user, path, mask, cmd): @@ -315,4 +315,4 @@ def rm_job(user, return ret -rm = rm_job # pylint: disable=C0103 +rm = salt.utils.alias_function(rm_job, 'rm') diff --git a/salt/modules/iptables.py b/salt/modules/iptables.py index e556898019..178e2c6f0d 100644 --- a/salt/modules/iptables.py +++ b/salt/modules/iptables.py @@ -261,6 +261,9 @@ def build_rule(table='filter', chain=None, command=None, position='', full=None, del kwargs[multiport_arg] if 'comment' in kwargs: + if '-m comment' not in rule: + rule.append('-m comment') + rule.append('--comment "{0}"'.format(kwargs['comment'])) del kwargs['comment'] diff --git a/salt/modules/junos.py b/salt/modules/junos.py index 964326d090..6160e9a12b 100644 --- a/salt/modules/junos.py +++ b/salt/modules/junos.py @@ -52,17 +52,16 @@ def facts_refresh(): Reload the facts dictionary from the device. Usually only needed if the device configuration is changed by some other actor. ''' - - return __opts__['proxymodule']['junos.refresh']() + return __proxy__['junos.refresh']() def call_rpc(): - return __opts__['proxymodule']['junos.rpc']() + return __proxy__['junos.rpc']() def set_hostname(hostname=None, commit_change=True): - conn = __opts__['proxymodule']['junos.conn']() + conn = __proxy__['junos.conn']() ret = dict() if hostname is None: ret['out'] = False @@ -84,7 +83,7 @@ def set_hostname(hostname=None, commit_change=True): def commit(): - conn = __opts__['proxymodule']['junos.conn']() + conn = __proxy__['junos.conn']() ret = {} commit_ok = conn.cu.commit_check() if commit_ok: @@ -104,7 +103,7 @@ def commit(): def rollback(): ret = dict() - conn = __opts__['proxymodule']['junos.conn']() + conn = __proxy__['junos.conn']() ret['out'] = conn.cu.rollback(0) @@ -118,7 +117,7 @@ def rollback(): def diff(): - conn = __opts__['proxymodule']['junos.conn']() + conn = __proxy__['junos.conn']() ret = dict() ret['out'] = True ret['message'] = conn.cu.diff() @@ -128,7 +127,18 @@ def diff(): def ping(): - conn = __opts__['proxymodule']['junos.conn']() + conn = __proxy__['junos.conn']() ret = dict() ret['message'] = conn.probe() ret['out'] = True + + return ret + + +def cli(command): + + conn = __proxy__['junos.conn']() + ret = dict() + ret['message'] = conn.cli(command) + ret['out'] = True + return ret diff --git a/salt/modules/lxc.py b/salt/modules/lxc.py index 3e6d03145c..e5ee11d237 100644 --- a/salt/modules/lxc.py +++ b/salt/modules/lxc.py @@ -2612,7 +2612,7 @@ def destroy(name, stop=False, path=None): return _change_state('lxc-destroy', name, None, path=path) # Compatibility between LXC and nspawn -remove = destroy +remove = salt.utils.alias_function(destroy, 'remove') def exists(name, path=None): @@ -2955,7 +2955,7 @@ def set_password(name, users, password, encrypted=True, path=None): ) return True -set_pass = set_password +set_pass = salt.utils.alias_function(set_password, 'set_pass') def update_lxc_conf(name, lxc_conf, lxc_conf_unset, path=None): @@ -4272,7 +4272,7 @@ def copy_to(name, source, dest, overwrite=False, makedirs=False, path=None): overwrite=overwrite, makedirs=makedirs) -cp = copy_to +cp = salt.utils.alias_function(copy_to, 'cp') def read_conf(conf_file, out_format='simple'): diff --git a/salt/modules/macports.py b/salt/modules/macports.py index c7e8cfe208..6e8e6dfd2e 100644 --- a/salt/modules/macports.py +++ b/salt/modules/macports.py @@ -189,7 +189,7 @@ def latest_version(*names, **kwargs): return ret # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def remove(name=None, pkgs=None, **kwargs): diff --git a/salt/modules/match.py b/salt/modules/match.py index 2e7eed2d68..1b30357383 100644 --- a/salt/modules/match.py +++ b/salt/modules/match.py @@ -333,7 +333,7 @@ def filter_by(lookup, expr_form='compound', minion_id=None): for key in lookup: if minion_id and expr_funcs[expr_form](key, minion_id): return lookup[key] - elif expr_funcs[expr_form](key, minion_id): + elif expr_funcs[expr_form](key): return lookup[key] return None diff --git a/salt/modules/memcached.py b/salt/modules/memcached.py index 42905a0d86..858647f4fa 100644 --- a/salt/modules/memcached.py +++ b/salt/modules/memcached.py @@ -12,6 +12,7 @@ from __future__ import absolute_import import logging # Import salt libs +import salt.utils from salt.exceptions import CommandExecutionError, SaltInvocationError from salt.ext.six import integer_types @@ -234,7 +235,7 @@ def increment(key, delta=1, host=DEFAULT_HOST, port=DEFAULT_PORT): except ValueError: raise SaltInvocationError('Delta value must be an integer') -incr = increment +incr = salt.utils.alias_function(increment, 'incr') def decrement(key, delta=1, host=DEFAULT_HOST, port=DEFAULT_PORT): @@ -265,4 +266,4 @@ def decrement(key, delta=1, host=DEFAULT_HOST, port=DEFAULT_PORT): except ValueError: raise SaltInvocationError('Delta value must be an integer') -decr = decrement +decr = salt.utils.alias_function(decrement, 'decr') diff --git a/salt/modules/mod_random.py b/salt/modules/mod_random.py index 23933ba920..9a4a18a30e 100644 --- a/salt/modules/mod_random.py +++ b/salt/modules/mod_random.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- ''' +Provides access to randomness generators. +========================================= + .. versionadded:: 2014.7.0 -Provides access to randomness generators. ''' from __future__ import absolute_import # Import python libs diff --git a/salt/modules/network.py b/salt/modules/network.py index 206548aee9..b629ccdf4e 100644 --- a/salt/modules/network.py +++ b/salt/modules/network.py @@ -728,7 +728,7 @@ def hw_addr(iface): return salt.utils.network.hw_addr(iface) # Alias hwaddr to preserve backward compat -hwaddr = hw_addr +hwaddr = salt.utils.alias_function(hw_addr, 'hwaddr') def interface(iface): @@ -874,7 +874,7 @@ def ip_addrs(interface=None, include_loopback=False, cidr=None): else: return addrs -ipaddrs = ip_addrs +ipaddrs = salt.utils.alias_function(ip_addrs, 'ipaddrs') def ip_addrs6(interface=None, include_loopback=False, cidr=None): @@ -898,7 +898,7 @@ def ip_addrs6(interface=None, include_loopback=False, cidr=None): else: return addrs -ipaddrs6 = ip_addrs6 +ipaddrs6 = salt.utils.alias_function(ip_addrs6, 'ipaddrs6') def get_hostname(): diff --git a/salt/modules/nspawn.py b/salt/modules/nspawn.py index 30d370c07c..e100293728 100644 --- a/salt/modules/nspawn.py +++ b/salt/modules/nspawn.py @@ -862,7 +862,7 @@ def list_running(): # 'machinectl list' shows only running containers, so allow this to work as an # alias to nspawn.list_running -list_ = list_running +list_ = salt.utils.alias_function(list_running, 'list_') def list_stopped(): @@ -1236,7 +1236,7 @@ def remove(name, stop=False): # Compatibility between LXC and nspawn -destroy = remove +destroy = salt.utils.alias_function(remove, 'destroy') @_ensure_exists @@ -1292,7 +1292,7 @@ def copy_to(name, source, dest, overwrite=False, makedirs=False): overwrite=overwrite, makedirs=makedirs) -cp = copy_to +cp = salt.utils.alias_function(copy_to, 'cp') # Everything below requres systemd >= 219 @@ -1455,4 +1455,4 @@ def pull_dkr(url, name, index): ''' return _pull_image('dkr', url, name, index=index) -pull_docker = pull_dkr +pull_docker = salt.utils.alias_function(pull_dkr, 'pull_docker') diff --git a/salt/modules/openbsdpkg.py b/salt/modules/openbsdpkg.py index ad24d78fa1..1b8b42a191 100644 --- a/salt/modules/openbsdpkg.py +++ b/salt/modules/openbsdpkg.py @@ -116,7 +116,7 @@ def latest_version(*names, **kwargs): return ret # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def version(*names, **kwargs): diff --git a/salt/modules/pacman.py b/salt/modules/pacman.py index 8c3710a245..13a5f98455 100644 --- a/salt/modules/pacman.py +++ b/salt/modules/pacman.py @@ -99,7 +99,7 @@ def latest_version(*names, **kwargs): return ret # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def upgrade_available(name): diff --git a/salt/modules/pagerduty.py b/salt/modules/pagerduty.py index 1bc4869d30..7d014bfeca 100644 --- a/salt/modules/pagerduty.py +++ b/salt/modules/pagerduty.py @@ -121,7 +121,7 @@ def list_windows(profile=None, api_key=None): # The long version, added for consistency -list_maintenance_windows = list_windows +list_maintenance_windows = salt.utils.alias_function(list_windows, 'list_maintenance_windows') def list_policies(profile=None, api_key=None): @@ -143,7 +143,7 @@ def list_policies(profile=None, api_key=None): # The long version, added for consistency -list_escalation_policies = list_policies +list_escalation_policies = salt.utils.alias_function(list_policies, 'list_escalation_policies') def create_event(service_key=None, description=None, details=None, diff --git a/salt/modules/parted.py b/salt/modules/parted.py index 1f839ca6e0..e8cd4958fe 100644 --- a/salt/modules/parted.py +++ b/salt/modules/parted.py @@ -443,7 +443,7 @@ def mkpart(device, part_type, fs_type=None, start=None, end=None): ''' _validate_device(device) - if not start or not end: + if start in [None, ''] or end in [None, '']: raise CommandExecutionError( 'partition.mkpart requires a start and an end' ) diff --git a/salt/modules/pillar.py b/salt/modules/pillar.py index b6c573dc52..2f3c7f57c3 100644 --- a/salt/modules/pillar.py +++ b/salt/modules/pillar.py @@ -114,7 +114,7 @@ def items(*args, **kwargs): return pillar.compile_pillar() # Allow pillar.data to also be used to return pillar data -data = items +data = salt.utils.alias_function(items, 'data') def _obfuscate_inner(var): diff --git a/salt/modules/pkgin.py b/salt/modules/pkgin.py index e62ac0d2c6..59c3fa802b 100644 --- a/salt/modules/pkgin.py +++ b/salt/modules/pkgin.py @@ -178,7 +178,7 @@ def latest_version(*names, **kwargs): # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def version(*names, **kwargs): diff --git a/salt/modules/pkgng.py b/salt/modules/pkgng.py index a8f11a2a7c..c3292c725d 100644 --- a/salt/modules/pkgng.py +++ b/salt/modules/pkgng.py @@ -193,7 +193,7 @@ def version(*names, **kwargs): ]) # Support pkg.info get version info, since this is the CLI usage -info = version +info = salt.utils.alias_function(version, 'info') def refresh_db(jail=None, chroot=None, force=False): @@ -236,7 +236,7 @@ def refresh_db(jail=None, chroot=None, force=False): # Support pkg.update to refresh the db, since this is the CLI usage -update = refresh_db +update = salt.utils.alias_function(refresh_db, 'update') def latest_version(*names, **kwargs): @@ -305,7 +305,7 @@ def latest_version(*names, **kwargs): # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def list_pkgs(versions_as_list=False, @@ -952,9 +952,9 @@ def remove(name=None, return salt.utils.compare_dicts(old, new) # Support pkg.delete to remove packages, since this is the CLI usage -delete = remove +delete = salt.utils.alias_function(remove, 'delete') # No equivalent to purge packages, use remove instead -purge = remove +purge = salt.utils.alias_function(remove, 'purge') def upgrade(*names, **kwargs): diff --git a/salt/modules/pkgutil.py b/salt/modules/pkgutil.py index 189a5c14f8..28d3216d18 100644 --- a/salt/modules/pkgutil.py +++ b/salt/modules/pkgutil.py @@ -226,7 +226,7 @@ def latest_version(*names, **kwargs): return ret # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def install(name=None, refresh=False, version=None, pkgs=None, **kwargs): diff --git a/salt/modules/rabbitmq.py b/salt/modules/rabbitmq.py index cbb3906caa..a660d3cacb 100644 --- a/salt/modules/rabbitmq.py +++ b/salt/modules/rabbitmq.py @@ -18,6 +18,7 @@ import salt.utils.itertools import salt.ext.six as six from salt.exceptions import SaltInvocationError from salt.ext.six.moves import range +from salt.exceptions import CommandExecutionError log = logging.getLogger(__name__) @@ -30,14 +31,15 @@ def __virtual__(): def _format_response(response, msg): + error = 'RabbitMQ command failed: {0}'.format(response) if isinstance(response, dict): if response['retcode'] != 0: - msg = 'Error' + raise CommandExecutionError(error) else: msg = response['stdout'] else: if 'Error' in response: - msg = 'Error' + raise CommandExecutionError(error) return { msg: response } @@ -703,8 +705,12 @@ def plugin_is_enabled(name, runas=None): if runas is None: runas = salt.utils.get_user() cmd = [_get_rabbitmq_plugin(), 'list', '-m', '-e'] - ret = __salt__['cmd.run'](cmd, runas=runas, python_shell=False) - return bool(name in ret) + ret = __salt__['cmd.run_all'](cmd, python_shell=False, runas=runas) + if ret['retcode'] != 0: + raise CommandExecutionError( + 'RabbitMQ command failed: {0}'.format(ret['stderr']) + ) + return bool(name in ret['stdout']) def enable_plugin(name, runas=None): diff --git a/salt/modules/reg.py b/salt/modules/reg.py index b87a40d4b4..ddcf85e567 100644 --- a/salt/modules/reg.py +++ b/salt/modules/reg.py @@ -67,9 +67,9 @@ class Registry(object): "HKU": _winreg.HKEY_USERS, } - self.reflection_mask = { - True: _winreg.KEY_ALL_ACCESS, - False: _winreg.KEY_ALL_ACCESS | _winreg.KEY_WOW64_64KEY, + self.registry_32 = { + True: _winreg.KEY_ALL_ACCESS | _winreg.KEY_WOW64_32KEY, + False: _winreg.KEY_ALL_ACCESS, } self.vtype = { @@ -106,7 +106,30 @@ def __virtual__(): return False -def read_key(hkey, path, key=None): +def _key_exists(hive, key, use_32bit_registry=False): + ''' + Check that the key is found in the registry + + :param str hive: The hive to connect to. + :param str key: The key to check + :param bool use_32bit_registry: Look in the 32bit portion of the registry + + :return: Returns True if found, False if not found + :rtype: bool + ''' + registry = Registry() + hkey = registry.hkeys[hive] + access_mask = registry.registry_32[use_32bit_registry] + + try: + handle = _winreg.OpenKey(hkey, key, 0, access_mask) + _winreg.CloseKey(handle) + return True + except WindowsError as exc: # pylint: disable=E0602 + return False + + +def read_key(hkey, path, key=None, use_32bit_registry=False): ''' .. important:: The name of this function is misleading and will be changed to reflect @@ -146,36 +169,41 @@ def read_key(hkey, path, key=None): 'removed in Salt Boron') return read_value(hive=hkey, key=path, - vname=key) + vname=key, + use_32bit_registry=use_32bit_registry) - return read_value(hive=hkey, key=path) + return read_value(hive=hkey, + key=path, + use_32bit_registry=use_32bit_registry) -def read_value(hive, key, vname=None): +def read_value(hive, key, vname=None, use_32bit_registry=False): r''' Reads a registry value entry or the default value for a key. - :param str hive: - The name of the hive. Can be one of the following - - HKEY_LOCAL_MACHINE or HKLM - - HKEY_CURRENT_USER or HKCU - - HKEY_USER or HKU + :param str hive: The name of the hive. Can be one of the following - :param str key: - The key (looks like a path) to the value name. + - HKEY_LOCAL_MACHINE or HKLM + - HKEY_CURRENT_USER or HKCU + - HKEY_USER or HKU - :param str vname: - The value name. These are the individual name/data pairs under the key. - If not passed, the key (Default) value will be returned + :param str key: The key (looks like a path) to the value name. - :return: - A dictionary containing the passed settings as well as the value_data if - successful. If unsuccessful, sets success to False + :param str vname: The value name. These are the individual name/data pairs + under the key. If not passed, the key (Default) value will be returned + + :param bool use_32bit_registry: Accesses the 32bit portion of the registry + on 64 bit installations. On 32bit machines this is ignored. + + :return: A dictionary containing the passed settings as well as the + value_data if successful. If unsuccessful, sets success to False + + If vname is not passed: + + - Returns the first unnamed value (Default) as a string. + - Returns none if first unnamed value is empty. + - Returns False if key not found. - If vname is not passed: - - Returns the first unnamed value (Default) as a string. - - Returns none if first unnamed value is empty. - - Returns False if key not found. :rtype: dict CLI Example: @@ -199,9 +227,10 @@ def read_value(hive, key, vname=None): registry = Registry() hkey = registry.hkeys[hive] + access_mask = registry.registry_32[use_32bit_registry] try: - handle = _winreg.OpenKey(hkey, key) + handle = _winreg.OpenKey(hkey, key, 0, access_mask) try: vdata, vtype = _winreg.QueryValueEx(handle, vname) if vdata or vdata in [0, '']: @@ -212,7 +241,6 @@ def read_value(hive, key, vname=None): except WindowsError as exc: # pylint: disable=E0602 ret['vdata'] = ('(value not set)') ret['vtype'] = 'REG_SZ' - ret['success'] = True except WindowsError as exc: # pylint: disable=E0602 log.debug(exc) log.debug('Cannot find key: {0}\\{1}'.format(hive, key)) @@ -222,7 +250,13 @@ def read_value(hive, key, vname=None): return ret -def set_key(hkey, path, value, key=None, vtype='REG_DWORD', reflection=True): +def set_key(hkey, + path, + value, + key=None, + vtype='REG_DWORD', + reflection=True, + use_32bit_registry=False): ''' .. important :: The name of this function is misleading and will be changed to reflect @@ -257,46 +291,57 @@ def set_key(hkey, path, value, key=None, vtype='REG_DWORD', reflection=True): key=path, vname=key, vdata=value, - vtype=vtype) + vtype=vtype, + use_32bit_registry=use_32bit_registry) - return set_value(hive=hkey, key=path, vdata=value, vtype=vtype) + return set_value(hive=hkey, + key=path, + vdata=value, + vtype=vtype, + use_32bit_registry=use_32bit_registry) -def set_value(hive, key, vname=None, vdata=None, vtype='REG_SZ', reflection=True): +def set_value(hive, + key, + vname=None, + vdata=None, + vtype='REG_SZ', + reflection=True, + use_32bit_registry=False): ''' Sets a registry value entry or the default value for a key. - :param str hive: - The name of the hive. Can be one of the following - - HKEY_LOCAL_MACHINE or HKLM - - HKEY_CURRENT_USER or HKCU - - HKEY_USER or HKU + :param str hive: The name of the hive. Can be one of the following - :param str key: - The key (looks like a path) to the value name. + - HKEY_LOCAL_MACHINE or HKLM + - HKEY_CURRENT_USER or HKCU + - HKEY_USER or HKU - :param str vname: - The value name. These are the individual name/data pairs under the key. - If not passed, the key (Default) value will be set. + :param str key: The key (looks like a path) to the value name. - :param str vdata: - The value data to be set. + :param str vname: The value name. These are the individual name/data pairs + under the key. If not passed, the key (Default) value will be set. - :param str vtype: - The value type. Can be one of the following: - - REG_BINARY - - REG_DWORD - - REG_EXPAND_SZ - - REG_MULTI_SZ - - REG_SZ + :param str vdata: The value data to be set. - :param bool reflection: - A boolean value indicating that the value should also be set in the - Wow6432Node portion of the registry. Only applies to 64 bit Windows. - This setting is ignored for 32 bit Windows. + :param str vtype: The value type. Can be one of the following: - :return: - Returns True if successful, False if not + - REG_BINARY + - REG_DWORD + - REG_EXPAND_SZ + - REG_MULTI_SZ + - REG_SZ + + :param bool reflection: A boolean value indicating that the value should + also be set in the Wow6432Node portion of the registry. Only applies to 64 + bit Windows. This setting is ignored for 32 bit Windows. + + .. deprecated:: 2015.8.2 + Use `use_32bit_registry` instead. The parameter seems to have no effect + since Windows 7 / Windows 2008R2 removed support for reflection. The + parameter will be removed in Boron. + + :return: Returns True if successful, False if not :rtype: bool CLI Example: @@ -306,21 +351,29 @@ def set_value(hive, key, vname=None, vdata=None, vtype='REG_SZ', reflection=True salt '*' reg.set_value HKEY_LOCAL_MACHINE 'SOFTWARE\\Salt' 'version' '2015.5.2' ''' registry = Registry() - hive = registry.hkeys[hive] + hkey = registry.hkeys[hive] vtype = registry.vtype[vtype] - access_mask = registry.reflection_mask[reflection] + access_mask = registry.registry_32[use_32bit_registry] try: - handle = _winreg.CreateKeyEx(hive, key, 0, access_mask) + handle = _winreg.CreateKeyEx(hkey, key, 0, access_mask) + if vtype == registry.vtype['REG_SZ']\ + or vtype == registry.vtype['REG_BINARY']: + vdata = str(vdata) _winreg.SetValueEx(handle, vname, 0, vtype, vdata) _winreg.CloseKey(handle) return True - except (WindowsError, ValueError) as exc: # pylint: disable=E0602 + except (WindowsError, ValueError, TypeError) as exc: # pylint: disable=E0602 log.error(exc, exc_info=True) return False -def create_key(hkey, path, key=None, value=None, reflection=True): +def create_key(hkey, + path, + key=None, + value=None, + reflection=True, + use_32bit_registry=False): ''' .. important :: The name of this function is misleading and will be changed to reflect @@ -353,12 +406,17 @@ def create_key(hkey, path, key=None, value=None, reflection=True): key=path, vname=key, vdata=value, - vtype='REG_SZ') + use_32bit_registry=use_32bit_registry) - return set_value(hive=hkey, key=path) + return set_value(hive=hkey, key=path, use_32bit_registry=use_32bit_registry) -def delete_key(hkey, path, key=None, reflection=True, force=False): +def delete_key(hkey, + path, + key=None, + reflection=True, + force=False, + use_32bit_registry=False): ''' .. important:: The name of this function is misleading and will be changed to reflect @@ -383,34 +441,31 @@ def delete_key(hkey, path, key=None, reflection=True, force=False): salt '*' reg.delete_key HKEY_CURRENT_USER 'SOFTWARE\\Salt' - :param str hkey: (will be changed to hive) - The name of the hive. Can be one of the following - - HKEY_LOCAL_MACHINE or HKLM - - HKEY_CURRENT_USER or HKCU - - HKEY_USER or HKU + :param str hkey: (will be changed to hive) The name of the hive. Can be one + of the following - :param str path: (will be changed to key) - The key (looks like a path) to remove. + - HKEY_LOCAL_MACHINE or HKLM + - HKEY_CURRENT_USER or HKCU + - HKEY_USER or HKU - :param str key: (used incorrectly) - Will be removed in Boron + :param str path: (will be changed to key) The key (looks like a path) to + remove. - :param bool reflection: - A boolean value indicating that the value should also be removed from - the Wow6432Node portion of the registry. Only applies to 64 bit Windows. - This setting is ignored for 32 bit Windows. + :param str key: (used incorrectly) Will be removed in Boron - Only applies to delete value. If the key parameter is passed, this - function calls delete_value instead. Will be changed in Boron. + :param bool reflection: A boolean value indicating that the value should + also be removed from the Wow6432Node portion of the registry. Only applies + to 64 bit Windows. This setting is ignored for 32 bit Windows. - :param bool force: - A boolean value indicating that all subkeys should be removed as well. - If this is set to False (default) and there are subkeys, the delete_key - function will fail. + Only applies to delete value. If the key parameter is passed, this function + calls delete_value instead. Will be changed in Boron. - :return: - Returns True if successful, False if not - If force=True, the results of delete_key_recursive are returned. + :param bool force: A boolean value indicating that all subkeys should be + removed as well. If this is set to False (default) and there are subkeys, + the delete_key function will fail. + + :return: Returns True if successful, False if not. If force=True, the + results of delete_key_recursive are returned. :rtype: bool ''' @@ -422,42 +477,46 @@ def delete_key(hkey, path, key=None, reflection=True, force=False): return delete_value(hive=hkey, key=path, vname=key, - reflection=reflection) + reflection=reflection, + use_32bit_registry=use_32bit_registry) if force: - return delete_key_recursive(hkey, path) + return delete_key_recursive(hkey, + path, + use_32bit_registry=use_32bit_registry) registry = Registry() hive = registry.hkeys[hkey] key = path + access_mask = registry.registry_32[use_32bit_registry] try: # Can't use delete_value to delete a key - _winreg.DeleteKey(hive, key) + key_handle = _winreg.OpenKey(hive, key, 0, access_mask) + _winreg.DeleteKey(key_handle, '') + _winreg.CloseKey(key_handle) return True except WindowsError as exc: # pylint: disable=E0602 log.error(exc, exc_info=True) return False -def delete_key_recursive(hive, key): +def delete_key_recursive(hive, key, use_32bit_registry=False): ''' .. versionadded:: 2015.5.4 Delete a registry key to include all subkeys. - :param hive: - The name of the hive. Can be one of the following - - HKEY_LOCAL_MACHINE or HKLM - - HKEY_CURRENT_USER or HKCU - - HKEY_USER or HKU + :param hive: The name of the hive. Can be one of the following - :param key: - The key to remove (looks like a path) + - HKEY_LOCAL_MACHINE or HKLM + - HKEY_CURRENT_USER or HKCU + - HKEY_USER or HKU - :return: - A dictionary listing the keys that deleted successfully as well as those - that failed to delete. + :param key: The key to remove (looks like a path) + + :return: A dictionary listing the keys that deleted successfully as well as + those that failed to delete. :rtype: dict The following example will remove ``salt`` and all its subkeys from the @@ -469,6 +528,15 @@ def delete_key_recursive(hive, key): salt '*' reg.delete_key_recursive HKLM SOFTWARE\\salt ''' + # Instantiate the registry object + registry = Registry() + hkey = registry.hkeys[hive] + key_path = key + access_mask = registry.registry_32[use_32bit_registry] + + if not _key_exists(hive, key, use_32bit_registry): + return False + # Functions for traversing the registry tree def subkeys(key): i = 0 @@ -480,70 +548,61 @@ def delete_key_recursive(hive, key): except WindowsError: # pylint: disable=E0602 break - def traverse_registry_tree(hkey, keypath, ret): - key = _winreg.OpenKey(hkey, keypath, 0, _winreg.KEY_READ) + def traverse_registry_tree(hkey, keypath, ret, access_mask): + key = _winreg.OpenKey(hkey, keypath, 0, access_mask) for subkeyname in subkeys(key): subkeypath = r'{0}\{1}'.format(keypath, subkeyname) - ret = traverse_registry_tree(hkey, subkeypath, ret) + ret = traverse_registry_tree(hkey, subkeypath, ret, access_mask) ret.append('{0}'.format(subkeypath)) return ret - # Instantiate the registry object - registry = Registry() - hkey = registry.hkeys[hive] - keypath = key - # Get a reverse list of registry keys to be deleted key_list = [] - key_list = traverse_registry_tree(hkey, keypath, key_list) + key_list = traverse_registry_tree(hkey, key_path, key_list, access_mask) + # Add the top level key last, all subkeys must be deleted first + key_list.append(r'{0}'.format(key_path)) ret = {'Deleted': [], 'Failed': []} - # Delete all subkeys - for keypath in key_list: + # Delete all sub_keys + for sub_key_path in key_list: try: - _winreg.DeleteKey(hkey, keypath) - ret['Deleted'].append(r'{0}\{1}'.format(hive, keypath)) + key_handle = _winreg.OpenKey(hkey, sub_key_path, 0, access_mask) + _winreg.DeleteKey(key_handle, '') + ret['Deleted'].append(r'{0}\{1}'.format(hive, sub_key_path)) except WindowsError as exc: # pylint: disable=E0602 log.error(exc, exc_info=True) - ret['Failed'].append(r'{0}\{1} {2}'.format(hive, key, exc)) - - # Delete the key now that all the subkeys are deleted - try: - _winreg.DeleteKey(hkey, key) - ret['Deleted'].append(r'{0}\{1}'.format(hive, key)) - except WindowsError as exc: # pylint: disable=E0602 - log.error(exc, exc_info=True) - ret['Failed'].append(r'{0}\{1} {2}'.format(hive, key, exc)) + ret['Failed'].append(r'{0}\{1} {2}'.format(hive, sub_key_path, exc)) return ret -def delete_value(hive, key, vname=None, reflection=True): +def delete_value(hive, key, vname=None, reflection=True, use_32bit_registry=False): ''' Delete a registry value entry or the default value for a key. - :param str hive: - The name of the hive. Can be one of the following - - HKEY_LOCAL_MACHINE or HKLM - - HKEY_CURRENT_USER or HKCU - - HKEY_USER or HKU + :param str hive: The name of the hive. Can be one of the following - :param str key: - The key (looks like a path) to the value name. + - HKEY_LOCAL_MACHINE or HKLM + - HKEY_CURRENT_USER or HKCU + - HKEY_USER or HKU - :param str vname: - The value name. These are the individual name/data pairs under the key. - If not passed, the key (Default) value will be deleted. + :param str key: The key (looks like a path) to the value name. - :param bool reflection: - A boolean value indicating that the value should also be set in the - Wow6432Node portion of the registry. Only applies to 64 bit Windows. - This setting is ignored for 32 bit Windows. + :param str vname: The value name. These are the individual name/data pairs + under the key. If not passed, the key (Default) value will be deleted. - :return: - Returns True if successful, False if not + :param bool reflection: A boolean value indicating that the value should + also be set in the Wow6432Node portion of the registry. Only applies to 64 + bit Windows. This setting is ignored for 32 bit Windows. + + .. deprecated:: 2015.8.2 + Use `use_32bit_registry` instead. The parameter seems to have no effect + since Windows 7 / Windows 2008R2 removed support for reflection. The + parameter will be removed in Boron. + + :return: Returns True if successful, False if not :rtype: bool CLI Example: @@ -554,7 +613,7 @@ def delete_value(hive, key, vname=None, reflection=True): ''' registry = Registry() hive = registry.hkeys[hive] - access_mask = registry.reflection_mask[reflection] + access_mask = registry.registry_32[use_32bit_registry] try: handle = _winreg.OpenKey(hive, key, 0, access_mask) @@ -562,6 +621,5 @@ def delete_value(hive, key, vname=None, reflection=True): _winreg.CloseKey(handle) return True except WindowsError as exc: # pylint: disable=E0602 - _winreg.CloseKey(handle) log.error(exc, exc_info=True) return False diff --git a/salt/modules/rh_ip.py b/salt/modules/rh_ip.py index 3239315f9d..3f7358bb23 100644 --- a/salt/modules/rh_ip.py +++ b/salt/modules/rh_ip.py @@ -325,9 +325,9 @@ def _parse_settings_bond_1(opts, iface, bond_def): if 'use_carrier' in opts: if opts['use_carrier'] in _CONFIG_TRUE: - bond.update({'use_carrier': 'on'}) + bond.update({'use_carrier': '1'}) elif opts['use_carrier'] in _CONFIG_FALSE: - bond.update({'use_carrier': 'off'}) + bond.update({'use_carrier': '0'}) else: valid = _CONFIG_TRUE + _CONFIG_FALSE _raise_error_iface(iface, 'use_carrier', valid) @@ -411,9 +411,9 @@ def _parse_settings_bond_3(opts, iface, bond_def): if 'use_carrier' in opts: if opts['use_carrier'] in _CONFIG_TRUE: - bond.update({'use_carrier': 'on'}) + bond.update({'use_carrier': '1'}) elif opts['use_carrier'] in _CONFIG_FALSE: - bond.update({'use_carrier': 'off'}) + bond.update({'use_carrier': '0'}) else: valid = _CONFIG_TRUE + _CONFIG_FALSE _raise_error_iface(iface, 'use_carrier', valid) @@ -455,9 +455,9 @@ def _parse_settings_bond_4(opts, iface, bond_def): if 'use_carrier' in opts: if opts['use_carrier'] in _CONFIG_TRUE: - bond.update({'use_carrier': 'on'}) + bond.update({'use_carrier': '1'}) elif opts['use_carrier'] in _CONFIG_FALSE: - bond.update({'use_carrier': 'off'}) + bond.update({'use_carrier': '0'}) else: valid = _CONFIG_TRUE + _CONFIG_FALSE _raise_error_iface(iface, 'use_carrier', valid) @@ -498,9 +498,9 @@ def _parse_settings_bond_5(opts, iface, bond_def): if 'use_carrier' in opts: if opts['use_carrier'] in _CONFIG_TRUE: - bond.update({'use_carrier': 'on'}) + bond.update({'use_carrier': '1'}) elif opts['use_carrier'] in _CONFIG_FALSE: - bond.update({'use_carrier': 'off'}) + bond.update({'use_carrier': '0'}) else: valid = _CONFIG_TRUE + _CONFIG_FALSE _raise_error_iface(iface, 'use_carrier', valid) @@ -534,9 +534,9 @@ def _parse_settings_bond_6(opts, iface, bond_def): if 'use_carrier' in opts: if opts['use_carrier'] in _CONFIG_TRUE: - bond.update({'use_carrier': 'on'}) + bond.update({'use_carrier': '1'}) elif opts['use_carrier'] in _CONFIG_FALSE: - bond.update({'use_carrier': 'off'}) + bond.update({'use_carrier': '0'}) else: valid = _CONFIG_TRUE + _CONFIG_FALSE _raise_error_iface(iface, 'use_carrier', valid) diff --git a/salt/modules/saltutil.py b/salt/modules/saltutil.py index 711b15fd42..04d3854efc 100644 --- a/salt/modules/saltutil.py +++ b/salt/modules/saltutil.py @@ -427,7 +427,7 @@ def sync_output(saltenv=None, refresh=True): refresh_modules() return ret -sync_outputters = sync_output +sync_outputters = salt.utils.alias_function(sync_output, 'sync_outputters') def sync_utils(saltenv=None, refresh=True): @@ -554,7 +554,7 @@ def refresh_pillar(): ret = False # Effectively a no-op, since we can't really return without an event system return ret -pillar_refresh = refresh_pillar +pillar_refresh = salt.utils.alias_function(refresh_pillar, 'pillar_refresh') def refresh_modules(async=True): diff --git a/salt/modules/schedule.py b/salt/modules/schedule.py index 2852883c95..278b1f5fbc 100644 --- a/salt/modules/schedule.py +++ b/salt/modules/schedule.py @@ -350,7 +350,7 @@ def build_schedule_item(name, **kwargs): schedule[name]['splay'] = kwargs['splay'] for item in ['range', 'when', 'once', 'once_fmt', 'cron', 'returner', - 'return_config', 'return_kwargs', 'until']: + 'return_config', 'return_kwargs', 'until', 'enabled']: if item in kwargs: schedule[name][item] = kwargs[item] diff --git a/salt/modules/solarisips.py b/salt/modules/solarisips.py index 647ebd169b..9aa8a2fb46 100644 --- a/salt/modules/solarisips.py +++ b/salt/modules/solarisips.py @@ -259,7 +259,7 @@ def latest_version(name, **kwargs): return '' # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def get_fmri(name, **kwargs): diff --git a/salt/modules/solarispkg.py b/salt/modules/solarispkg.py index a3cac7fe49..8c9ff46ed2 100644 --- a/salt/modules/solarispkg.py +++ b/salt/modules/solarispkg.py @@ -150,7 +150,7 @@ def latest_version(*names, **kwargs): return ret # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def upgrade_available(name): diff --git a/salt/modules/ssh_service.py b/salt/modules/ssh_service.py new file mode 100644 index 0000000000..96b83e6ab4 --- /dev/null +++ b/salt/modules/ssh_service.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +''' +Provide the service module for the proxy-minion SSH sample +.. versionadded:: 2015.8.2 +''' +# Import python libs +from __future__ import absolute_import +import logging + +__proxyenabled__ = ['ssh_sample'] + +log = logging.getLogger(__name__) + +__func_alias__ = { + 'list_': 'list' +} + + +# Define the module's virtual name +__virtualname__ = 'service' + + +def __virtual__(): + ''' + Only work on systems that are a proxy minion + ''' + if __grains__['os'] == 'proxy': + return __virtualname__ + return False + + +def get_all(): + ''' + Return a list of all available services + + CLI Example: + + .. code-block:: bash + + salt '*' service.get_all + ''' + proxy_fn = 'ssh_sample.service_list' + return __proxy__[proxy_fn]() + + +def list_(): + ''' + Return a list of all available services. + + CLI Example: + + .. code-block:: bash + + salt '*' service.list + ''' + return get_all() + + +def start(name, sig=None): + ''' + Start the specified service on the rest_sample + + CLI Example: + + .. code-block:: bash + + salt '*' service.start + ''' + + proxy_fn = 'ssh_sample.service_start' + return __proxy__[proxy_fn](name) + + +def stop(name, sig=None): + ''' + Stop the specified service on the rest_sample + + CLI Example: + + .. code-block:: bash + + salt '*' service.stop + ''' + proxy_fn = 'ssh_sample.service_stop' + return __proxy__[proxy_fn](name) + + +def restart(name, sig=None): + ''' + Restart the specified service with rest_sample + CLI Example: + + .. code-block:: bash + + salt '*' service.restart + ''' + + proxy_fn = 'ssh_sample.service_restart' + return __proxy__[proxy_fn](name) + + +def status(name, sig=None): + ''' + Return the status for a service via rest_sample, returns a bool + whether the service is running. + + CLI Example: + + .. code-block:: bash + + salt '*' service.status + ''' + + proxy_fn = 'ssh_sample.service_status' + resp = __proxy__[proxy_fn](name) + if resp['comment'] == 'stopped': + return False + if resp['comment'] == 'running': + return True + + +def running(name, sig=None): + ''' + Return whether this service is running. + ''' + return status(name).get(name, False) + + +def enabled(name, sig=None): + ''' + Only the 'redbull' service is 'enabled' in the test + ''' + return name == 'redbull' diff --git a/salt/modules/test.py b/salt/modules/test.py index 5b4dec3d8e..f6b7059f3f 100644 --- a/salt/modules/test.py +++ b/salt/modules/test.py @@ -190,7 +190,7 @@ def versions_report(): return '\n'.join(salt.version.versions_report()) -versions = versions_report +versions = salt.utils.alias_function(versions_report, 'versions') def conf_test(): diff --git a/salt/modules/win_network.py b/salt/modules/win_network.py index d9c95a352c..4dcd8edbbd 100644 --- a/salt/modules/win_network.py +++ b/salt/modules/win_network.py @@ -231,7 +231,7 @@ def hw_addr(iface): return salt.utils.network.hw_addr(iface) # Alias hwaddr to preserve backward compat -hwaddr = hw_addr +hwaddr = salt.utils.alias_function(hw_addr, 'hwaddr') def subnets(): @@ -275,7 +275,7 @@ def ip_addrs(interface=None, include_loopback=False): return salt.utils.network.ip_addrs(interface=interface, include_loopback=include_loopback) -ipaddrs = ip_addrs +ipaddrs = salt.utils.alias_function(ip_addrs, 'ipaddrs') def ip_addrs6(interface=None, include_loopback=False): @@ -293,7 +293,7 @@ def ip_addrs6(interface=None, include_loopback=False): return salt.utils.network.ip_addrs6(interface=interface, include_loopback=include_loopback) -ipaddrs6 = ip_addrs6 +ipaddrs6 = salt.utils.alias_function(ip_addrs6, 'ipaddrs6') def connect(host, port=None, **kwargs): diff --git a/salt/modules/win_pkg.py b/salt/modules/win_pkg.py index 04ed31c104..b43ced4f06 100644 --- a/salt/modules/win_pkg.py +++ b/salt/modules/win_pkg.py @@ -110,7 +110,7 @@ def latest_version(*names, **kwargs): return ret # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def upgrade_available(name): diff --git a/salt/modules/win_shadow.py b/salt/modules/win_shadow.py index 167b28787f..d1665a322a 100644 --- a/salt/modules/win_shadow.py +++ b/salt/modules/win_shadow.py @@ -24,35 +24,91 @@ def info(name): Return information for the specified user This is just returns dummy data so that salt states can work. + :param str name: The name of the user account to show. + CLI Example: .. code-block:: bash salt '*' shadow.info root ''' - ret = { - 'name': name, - 'passwd': '', - 'lstchg': '', - 'min': '', - 'max': '', - 'warn': '', - 'inact': '', - 'expire': ''} + info = __salt__['user.info'](name=name) + + ret = {'name': name, + 'passwd': '', + 'lstchg': '', + 'min': '', + 'max': '', + 'warn': '', + 'inact': '', + 'expire': ''} + + if info: + ret = {'name': info['name'], + 'passwd': 'Unavailable', + 'lstchg': info['password_changed'], + 'min': '', + 'max': '', + 'warn': '', + 'inact': '', + 'expire': info['expiration_date']} + return ret +def set_expire(name, expire): + ''' + Set the expiration date for a user account. + + :param name: The name of the user account to edit. + + :param expire: The date the account will expire. + + :return: True if successful. False if unsuccessful. + :rtype: bool + ''' + return __salt__['user.update'](name, expiration_date=expire) + + +def require_password_change(name): + ''' + Require the user to change their password the next time they log in. + + :param name: The name of the user account to require a password change. + + :return: True if successful. False if unsuccessful. + :rtype: bool + ''' + return __salt__['user.update'](name, expired=True) + + +def unlock_account(name): + ''' + Unlocks a user account. + + :param name: The name of the user account to unlock. + + :return: True if successful. False if unsuccessful. + :rtype: bool + ''' + return __salt__['user.update'](name, unlock_account=True) + + def set_password(name, password): ''' Set the password for a named user. + :param str name: The name of the user account + + :param str password: The new password + + :return: True if successful. False if unsuccessful. + :rtype: bool + CLI Example: .. code-block:: bash salt '*' shadow.set_password root mysecretpassword ''' - cmd = ['net', 'user', name, password] - ret = __salt__['cmd.run_all'](cmd, python_shell=False) - - return not ret['retcode'] + return __salt__['user.update'](name=name, password=password) diff --git a/salt/modules/win_system.py b/salt/modules/win_system.py index b526a02b21..32cda532ae 100644 --- a/salt/modules/win_system.py +++ b/salt/modules/win_system.py @@ -401,7 +401,7 @@ def set_computer_desc(desc=None): return {'Computer Description': get_computer_desc()} -set_computer_description = set_computer_desc +set_computer_description = salt.utils.alias_function(set_computer_desc, 'set_computer_description') def get_system_info(): @@ -434,7 +434,7 @@ def get_computer_desc(): return desc if desc else False -get_computer_description = get_computer_desc +get_computer_description = salt.utils.alias_function(get_computer_desc, 'get_computer_description') def get_hostname(): diff --git a/salt/modules/win_useradd.py b/salt/modules/win_useradd.py index 0a7f9ed8e0..067738a6bd 100644 --- a/salt/modules/win_useradd.py +++ b/salt/modules/win_useradd.py @@ -15,6 +15,8 @@ Module for managing Windows Users This currently only works with local user accounts, not domain accounts ''' from __future__ import absolute_import +from datetime import datetime +import time try: from shlex import quote as _cmd_quote # pylint: disable=E0611 @@ -57,6 +59,59 @@ def __virtual__(): return False +def _get_date_time_format(dt_string): + ''' + Copied from win_system.py (_get_date_time_format) + + Function that detects the date/time format for the string passed. + + :param str dt_string: + A date/time string + + :return: The format of the passed dt_string + :rtype: str + ''' + valid_formats = [ + '%Y-%m-%d %I:%M:%S %p', + '%m-%d-%y %I:%M:%S %p', + '%m-%d-%Y %I:%M:%S %p', + '%m/%d/%y %I:%M:%S %p', + '%m/%d/%Y %I:%M:%S %p', + '%Y/%m/%d %I:%M:%S %p', + '%Y-%m-%d %I:%M:%S', + '%m-%d-%y %I:%M:%S', + '%m-%d-%Y %I:%M:%S', + '%m/%d/%y %I:%M:%S', + '%m/%d/%Y %I:%M:%S', + '%Y/%m/%d %I:%M:%S', + '%Y-%m-%d %I:%M %p', + '%m-%d-%y %I:%M %p', + '%m-%d-%Y %I:%M %p', + '%m/%d/%y %I:%M %p', + '%m/%d/%Y %I:%M %p', + '%Y/%m/%d %I:%M %p', + '%Y-%m-%d %I:%M', + '%m-%d-%y %I:%M', + '%m-%d-%Y %I:%M', + '%m/%d/%y %I:%M', + '%m/%d/%Y %I:%M', + '%Y/%m/%d %I:%M', + '%Y-%m-%d', + '%m-%d-%y', + '%m-%d-%Y', + '%m/%d/%y', + '%m/%d/%Y', + '%Y/%m/%d', + ] + for dt_format in valid_formats: + try: + datetime.strptime(dt_string, dt_format) + return dt_format + except ValueError: + continue + return False + + def add(name, password=None, fullname=False, @@ -147,7 +202,13 @@ def update(name, home=None, homedrive=None, logonscript=None, - profile=None): + profile=None, + expiration_date=None, + expired=None, + account_disabled=None, + unlock_account=None, + password_never_expires=None, + disallow_change_password=None): r''' Updates settings for the windows user. Name is the only required parameter. Settings will only be changed if the parameter is passed a value. @@ -179,6 +240,27 @@ def update(name, :param str profile: The path to the user's profile directory. + :param date expiration_date: The date and time when the account expires. Can + be a valid date/time string. To set to never expire pass the string 'Never'. + + :param bool expired: Pass `True` to expire the account. The user will be + prompted to change their password at the next logon. Pass `False` to mark + the account as 'not expired'. You can't use this to negate the expiration if + the expiration was caused by the account expiring. You'll have to change + the `expiration_date` as well. + + :param bool account_disabled: True disables the account. False enables the + account. + + :param bool unlock_account: True unlocks a locked user account. False is + ignored. + + :param bool password_never_expires: True sets the password to never expire. + False allows the password to expire. + + :param bool disallow_change_password: True blocks the user from changing + the password. False allows the user to change the password. + :return: True if successful. False is unsuccessful. :rtype: bool @@ -219,6 +301,39 @@ def update(name, user_info['full_name'] = fullname if profile: user_info['profile'] = profile + if expiration_date: + if expiration_date == 'Never': + user_info['acct_expires'] = win32netcon.TIMEQ_FOREVER + else: + date_format = _get_date_time_format(expiration_date) + if date_format: + dt_obj = datetime.strptime(expiration_date, date_format) + else: + return 'Invalid start_date' + user_info['acct_expires'] = time.mktime(dt_obj.timetuple()) + if expired is not None: + if expired: + user_info['password_expired'] = 1 + else: + user_info['password_expired'] = 0 + if account_disabled is not None: + if account_disabled: + user_info['flags'] |= win32netcon.UF_ACCOUNTDISABLE + else: + user_info['flags'] ^= win32netcon.UF_ACCOUNTDISABLE + if unlock_account is not None: + if unlock_account: + user_info['flags'] ^= win32netcon.UF_LOCKOUT + if password_never_expires is not None: + if password_never_expires: + user_info['flags'] |= win32netcon.UF_DONT_EXPIRE_PASSWD + else: + user_info['flags'] ^= win32netcon.UF_DONT_EXPIRE_PASSWD + if disallow_change_password is not None: + if disallow_change_password: + user_info['flags'] |= win32netcon.UF_PASSWD_CANT_CHANGE + else: + user_info['flags'] ^= win32netcon.UF_PASSWD_CANT_CHANGE # Apply new settings try: @@ -626,6 +741,14 @@ def info(name): - home - homedrive - groups + - password_changed + - successful_logon_attempts + - failed_logon_attempts + - last_logon + - account_disabled + - account_locked + - password_never_expires + - disallow_change_password - gid :rtype: dict @@ -658,6 +781,19 @@ def info(name): ret['active'] = (not bool(items['flags'] & win32netcon.UF_ACCOUNTDISABLE)) ret['logonscript'] = items['script_path'] ret['profile'] = items['profile'] + ret['failed_logon_attempts'] = items['bad_pw_count'] + ret['successful_logon_attempts'] = items['num_logons'] + secs = time.mktime(datetime.now().timetuple()) - items['password_age'] + ret['password_changed'] = datetime.fromtimestamp(secs). \ + strftime('%Y-%m-%d %H:%M:%S') + if items['last_logon'] == 0: + ret['last_logon'] = 'Never' + else: + ret['last_logon'] = datetime.fromtimestamp(items['last_logon']).\ + strftime('%Y-%m-%d %H:%M:%S') + ret['expiration_date'] = datetime.fromtimestamp(items['acct_expires']).\ + strftime('%Y-%m-%d %H:%M:%S') + ret['expired'] = items['password_expired'] == 1 if not ret['profile']: ret['profile'] = _get_userprofile_from_registry(name, ret['uid']) ret['home'] = items['home_dir'] @@ -665,9 +801,30 @@ def info(name): if not ret['home']: ret['home'] = ret['profile'] ret['groups'] = groups + if items['flags'] & win32netcon.UF_DONT_EXPIRE_PASSWD == 0: + ret['password_never_expires'] = False + else: + ret['password_never_expires'] = True + if items['flags'] & win32netcon.UF_ACCOUNTDISABLE == 0: + ret['account_disabled'] = False + else: + ret['account_disabled'] = True + if items['flags'] & win32netcon.UF_LOCKOUT == 0: + ret['account_locked'] = False + else: + ret['account_locked'] = True + if items['flags'] & win32netcon.UF_PASSWD_CANT_CHANGE == 0: + ret['disallow_change_password'] = False + else: + ret['disallow_change_password'] = True + ret['gid'] = '' - return ret + return ret + + else: + + return False def _get_userprofile_from_registry(user, sid): diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py index 0974597520..1a708dc116 100644 --- a/salt/modules/yumpkg.py +++ b/salt/modules/yumpkg.py @@ -439,7 +439,7 @@ def latest_version(*names, **kwargs): return ret # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def upgrade_available(name): @@ -1808,7 +1808,7 @@ def group_install(name, return install(pkgs=pkgs, **kwargs) -groupinstall = group_install +groupinstall = salt.utils.alias_function(group_install, 'groupinstall') def list_repos(basedir=None): @@ -1978,6 +1978,14 @@ def mod_repo(repo, basedir=None, **kwargs): del repo_opts[key] todelete.append(key) + # convert disabled=True to enabled=0 from pkgrepo state + if 'disabled' in repo_opts: + kw_disabled = repo_opts['disabled'] + if kw_disabled is True or str(kw_disabled).lower() == 'true': + repo_opts['enabled'] = 0 + del repo_opts['disabled'] + todelete.append('disabled') + # Add baseurl or mirrorlist to the 'todelete' list if the other was # specified in the repo_opts if 'mirrorlist' in repo_opts: diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py index 895b0d4d48..d2b461288d 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -95,7 +95,7 @@ def list_upgrades(refresh=True): return ret # Provide a list_updates function for those used to using zypper list-updates -list_updates = list_upgrades +list_updates = salt.utils.alias_function(list_upgrades, 'list_updates') def info_installed(*names): @@ -175,10 +175,10 @@ def info_available(*names, **kwargs): if nfo.get('name'): name = nfo.pop('name') ret[name] = nfo - if nfo.get("status"): - nfo['status'] = nfo.get("status").split(" ")[0] - if nfo.get("installed"): - nfo['installed'] = nfo.get("installed").lower() == "yes" and True or False + if nfo.get('status'): + nfo['status'] = nfo.get('status') + if nfo.get('installed'): + nfo['installed'] = nfo.get('installed').lower() == 'yes' and True or False return ret @@ -227,13 +227,17 @@ def latest_version(*names, **kwargs): for name in names: pkg_info = package_info.get(name, {}) if pkg_info.get('status', '').lower() in ['not installed', 'out-of-date']: - ret[name] = pkg_info + ret[name] = pkg_info.get('version') + + # Return a string if only one package name passed + if len(names) == 1 and len(ret): + return ret[names[0]] return ret # available_version is being deprecated -available_version = latest_version +available_version = salt.utils.alias_function(latest_version, 'available_version') def upgrade_available(name): diff --git a/salt/pillar/__init__.py b/salt/pillar/__init__.py index 2276f71522..4134843238 100644 --- a/salt/pillar/__init__.py +++ b/salt/pillar/__init__.py @@ -446,10 +446,12 @@ class Pillar(object): self.opts.get('nodegroups', {}), ): if saltenv not in matches: - matches[saltenv] = [] + matches[saltenv] = env_matches = [] + else: + env_matches = matches[saltenv] for item in data: - if isinstance(item, six.string_types): - matches[saltenv].append(item) + if isinstance(item, six.string_types) and item not in env_matches: + env_matches.append(item) return matches def render_pstate(self, sls, saltenv, mods, defaults=None): @@ -528,20 +530,20 @@ class Pillar(object): mods, defaults ) - if nstate: - if key: - nstate = { - key: nstate - } + if nstate: + if key: + nstate = { + key: nstate + } - state = merge( - state, - nstate, - self.merge_strategy, - self.opts.get('renderer', 'yaml')) + state = merge( + state, + nstate, + self.merge_strategy, + self.opts.get('renderer', 'yaml')) - if err: - errors += err + if err: + errors += err return state, mods, errors def render_pillar(self, matches): diff --git a/salt/pillar/git_pillar.py b/salt/pillar/git_pillar.py index 1589d2ad08..e75755bca5 100644 --- a/salt/pillar/git_pillar.py +++ b/salt/pillar/git_pillar.py @@ -397,9 +397,9 @@ def _legacy_git_pillar(minion_id, repo_string, pillar_dirs): log.warning('Unrecognized extra parameter: {0}'.format(key)) # environment is "different" from the branch - branch, _, environment = branch_env.partition(':') + cfg_branch, _, environment = branch_env.partition(':') - gitpil = _LegacyGitPillar(branch, repo_location, __opts__) + gitpil = _LegacyGitPillar(cfg_branch, repo_location, __opts__) branch = gitpil.branch if environment == '': @@ -413,7 +413,9 @@ def _legacy_git_pillar(minion_id, repo_string, pillar_dirs): pillar_dirs.setdefault(pillar_dir, {}) - if pillar_dirs[pillar_dir].get(branch, False): + if cfg_branch == '__env__' and branch not in ['master', 'base']: + gitpil.update() + elif pillar_dirs[pillar_dir].get(branch, False): return {} # we've already seen this combo pillar_dirs[pillar_dir].setdefault(branch, True) @@ -429,7 +431,7 @@ def _legacy_git_pillar(minion_id, repo_string, pillar_dirs): pil = Pillar(opts, __grains__, minion_id, branch) - return pil.compile_pillar() + return pil.compile_pillar(ext=False) def _update(branch, repo_location): diff --git a/salt/proxy/fx2.py b/salt/proxy/fx2.py index a194bd442f..fe7876b4dc 100644 --- a/salt/proxy/fx2.py +++ b/salt/proxy/fx2.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- ''' -====== -fx2.py -====== +Dell FX2 chassis +================ .. versionadded:: 2015.8.2 @@ -218,6 +217,13 @@ def chconfig(cmd, *args, **kwargs): for k in kwargs.keys(): if k.startswith('__pub_'): kwargs.pop(k) + + # Catch password reset + if cmd == 'change_password': + if 'username' in kwargs: + DETAILS['admin_username'] = kwargs['username'] + if 'password' in kwargs: + DETAILS['admin_password'] = kwargs['password'] if 'dracr.'+cmd not in __salt__: return {'retcode': -1, 'message': 'dracr.' + cmd + ' is not available'} else: diff --git a/salt/proxy/junos.py b/salt/proxy/junos.py index f753fae7f4..e804db886b 100644 --- a/salt/proxy/junos.py +++ b/salt/proxy/junos.py @@ -10,9 +10,9 @@ from __future__ import absolute_import import logging # Import 3rd-party libs -# import jnpr.junos -# import jnpr.junos.utils -# import jnpr.junos.utils.config +import jnpr.junos +import jnpr.junos.utils +import jnpr.junos.utils.config import json HAS_JUNOS = True @@ -22,17 +22,18 @@ thisproxy = {} log = logging.getLogger(__name__) -# def __init__(opts): -# ''' -# Open the connection to the Junos device, login, and bind to the -# Resource class -# ''' -# log.debug('Opening connection to junos') -# thisproxy['conn'] = jnpr.junos.Device(user=opts['proxy']['username'], -# host=opts['proxy']['host'], -# password=opts['proxy']['passwd']) -# thisproxy['conn'].open() -# thisproxy['conn'].bind(cu=jnpr.junos.utils.config.Config) + +def init(opts): + ''' + Open the connection to the Junos device, login, and bind to the + Resource class + ''' + log.debug('Opening connection to junos') + thisproxy['conn'] = jnpr.junos.Device(user=opts['proxy']['username'], + host=opts['proxy']['host'], + password=opts['proxy']['passwd']) + thisproxy['conn'].open() + thisproxy['conn'].bind(cu=jnpr.junos.utils.config.Config) def conn(): diff --git a/salt/proxy/ssh_sample.py b/salt/proxy/ssh_sample.py index 8ea54bc625..dbf08eaf74 100644 --- a/salt/proxy/ssh_sample.py +++ b/salt/proxy/ssh_sample.py @@ -51,6 +51,19 @@ def init(opts): return False +def ping(): + ''' + Required. + Ping the device on the other end of the connection + ''' + try: + out, err = DETAILS['server'].sendline('help') + return True + except TerminalException as e: + log.error(e) + return False + + def shutdown(opts): ''' Disconnect @@ -96,13 +109,11 @@ def package_list(): def package_install(name, **kwargs): ''' - Install a "package" on the REST server + Install a "package" on the ssh server ''' cmd = 'pkg_install ' + name if 'version' in kwargs: - cmd += '/'+kwargs['version'] - else: - cmd += '/1.0' + cmd += ' ' + kwargs['version'] # Send the command to execute out, err = DETAILS['server'].sendline(cmd) @@ -113,7 +124,7 @@ def package_install(name, **kwargs): def package_remove(name): ''' - Remove a "package" on the REST server + Remove a "package" on the ssh server ''' cmd = 'pkg_remove ' + name @@ -122,3 +133,63 @@ def package_remove(name): # "scrape" the output and return the right fields as a dict return parse(out) + + +def service_list(): + ''' + Start a "service" on the ssh server + + .. versionadded:: 2015.8.2 + ''' + cmd = 'ps' + + # Send the command to execute + out, err = DETAILS['server'].sendline(cmd) + + # "scrape" the output and return the right fields as a dict + return parse(out) + + +def service_start(name): + ''' + Start a "service" on the ssh server + + .. versionadded:: 2015.8.2 + ''' + cmd = 'start ' + name + + # Send the command to execute + out, err = DETAILS['server'].sendline(cmd) + + # "scrape" the output and return the right fields as a dict + return parse(out) + + +def service_stop(name): + ''' + Stop a "service" on the ssh server + + .. versionadded:: 2015.8.2 + ''' + cmd = 'stop ' + name + + # Send the command to execute + out, err = DETAILS['server'].sendline(cmd) + + # "scrape" the output and return the right fields as a dict + return parse(out) + + +def service_restart(name): + ''' + Restart a "service" on the ssh server + + .. versionadded:: 2015.8.2 + ''' + cmd = 'restart ' + name + + # Send the command to execute + out, err = DETAILS['server'].sendline(cmd) + + # "scrape" the output and return the right fields as a dict + return parse(out) diff --git a/salt/runners/pagerduty.py b/salt/runners/pagerduty.py index c2575ffa5e..595fa86ba0 100644 --- a/salt/runners/pagerduty.py +++ b/salt/runners/pagerduty.py @@ -120,7 +120,7 @@ def list_windows(profile=None, api_key=None): # The long version, added for consistency -list_maintenance_windows = list_windows +list_maintenance_windows = salt.utils.alias_function(list_windows, 'list_maintenance_windows') def list_policies(profile=None, api_key=None): @@ -142,7 +142,7 @@ def list_policies(profile=None, api_key=None): # The long version, added for consistency -list_escalation_policies = list_policies +list_escalation_policies = salt.utils.alias_function(list_policies, 'list_escalation_policies') def create_event(service_key=None, description=None, details=None, diff --git a/salt/runners/state.py b/salt/runners/state.py index c57a8c7668..637cfbced7 100644 --- a/salt/runners/state.py +++ b/salt/runners/state.py @@ -8,6 +8,7 @@ import logging # Import salt libs import salt.loader +import salt.utils import salt.utils.event from salt.exceptions import SaltInvocationError @@ -62,8 +63,8 @@ def orchestrate(mods, saltenv='base', test=None, exclude=None, pillar=None): return ret # Aliases for orchestrate runner -orch = orchestrate # pylint: disable=invalid-name -sls = orchestrate # pylint: disable=invalid-name +orch = salt.utils.alias_function(orchestrate, 'orch') +sls = salt.utils.alias_function(orchestrate, 'sls') def orchestrate_single(fun, name, test=None, queue=False, pillar=None, **kwargs): diff --git a/salt/spm/__init__.py b/salt/spm/__init__.py index 644eed2941..b7cc0cfa4f 100644 --- a/salt/spm/__init__.py +++ b/salt/spm/__init__.py @@ -653,6 +653,10 @@ class SPMClient(object): self.formula_conf = formula_conf formula_tar = tarfile.open(out_path, 'w:bz2') + + # Add FORMULA first, to speed up create_repo on large packages + formula_tar.add(formula_path, formula_conf['name'], filter=self._exclude) + try: formula_tar.add(self.abspath, formula_conf['name'], filter=self._exclude) except TypeError: diff --git a/salt/state.py b/salt/state.py index 49cc399c2a..55d3c565c0 100644 --- a/salt/state.py +++ b/salt/state.py @@ -48,7 +48,7 @@ import salt.utils.yamlloader as yamlloader # Import third party libs # pylint: disable=import-error,no-name-in-module,redefined-builtin import salt.ext.six as six -from salt.ext.six.moves import range +from salt.ext.six.moves import map, range # pylint: enable=import-error,no-name-in-module,redefined-builtin log = logging.getLogger(__name__) @@ -549,7 +549,7 @@ class Compiler(object): if isinstance(entry, dict): low_name = next(six.iterkeys(entry)) live['name'] = low_name - live.update(entry[low_name][0]) + list(map(live.update, entry[low_name])) else: live['name'] = entry live['name_order'] = name_order @@ -1228,7 +1228,7 @@ class State(object): if isinstance(entry, dict): low_name = next(six.iterkeys(entry)) live['name'] = low_name - live.update(entry[low_name][0]) + list(map(live.update, entry[low_name])) else: live['name'] = entry live['name_order'] = name_order diff --git a/salt/states/at.py b/salt/states/at.py index a7172b01ce..7344001e83 100644 --- a/salt/states/at.py +++ b/salt/states/at.py @@ -83,9 +83,9 @@ def present(name, timespec, tag=None, user=None, job=None): ret['comment'] = 'User: {0} is not exists'.format(user) ret['result'] = False return ret - ret['comment'] = __salt__['cmd.run']('{0}'.format(cmd), runas=user) + ret['comment'] = __salt__['cmd.run']('{0}'.format(cmd), runas=user, python_shell=True) else: - ret['comment'] = __salt__['cmd.run']('{0}'.format(cmd)) + ret['comment'] = __salt__['cmd.run']('{0}'.format(cmd), python_shell=True) return ret diff --git a/salt/states/blockdev.py b/salt/states/blockdev.py index 6b206b3259..09aa06d0fa 100644 --- a/salt/states/blockdev.py +++ b/salt/states/blockdev.py @@ -29,14 +29,17 @@ import os.path # Import salt libs import salt.utils +__virtualname__ = 'blockdev' + def __virtual__(): ''' - Only work on POSIX-like systems + Only load this module if the blockdev execution module is available ''' - if salt.utils.is_windows(): - return False - return True + if 'blockdev.tune' in __salt__: + return __virtualname__ + return (False, ('Cannot load the {0} state module: ' + 'blockdev execution module not found'.format(__virtualname__))) def tuned(name, **kwargs): @@ -146,25 +149,8 @@ def formatted(name, fs_type='ext4', **kwargs): ret['result'] = None return ret - cmd = 'mkfs -t {0} '.format(fs_type) - if 'inode_size' in kwargs: - if fs_type[:3] == 'ext': - cmd += '-i {0} '.format(kwargs['inode_size']) - elif fs_type == 'xfs': - cmd += '-i size={0} '.format(kwargs['inode_size']) - if 'lazy_itable_init' in kwargs: - if fs_type[:3] == 'ext': - cmd += '-E lazy_itable_init={0} '.format(kwargs['lazy_itable_init']) - - cmd += name - __salt__['cmd.run'](cmd).splitlines() - __salt__['cmd.run']('sync').splitlines() - blk = __salt__['cmd.run']('lsblk -o fstype {0}'.format(name)).splitlines() - - if len(blk) == 1: - current_fs = '' - else: - current_fs = blk[1] + __salt__['blockdev.format'](name, fs_type, **kwargs) + current_fs = __salt__['blockdev.fstype'](name) if current_fs == fs_type: ret['comment'] = ('{0} has been formatted ' diff --git a/salt/states/boto_elasticache.py b/salt/states/boto_elasticache.py index 705b8e8240..0f2ab9689d 100644 --- a/salt/states/boto_elasticache.py +++ b/salt/states/boto_elasticache.py @@ -2,7 +2,7 @@ ''' Manage Elasticache ================== -replication_group_description + .. versionadded:: 2014.7.0 Create, destroy and update Elasticache clusters. Be aware that this interacts diff --git a/salt/states/boto_route53.py b/salt/states/boto_route53.py index 720617799e..866c9b410d 100644 --- a/salt/states/boto_route53.py +++ b/salt/states/boto_route53.py @@ -71,6 +71,12 @@ passed in as a dict, or as a string to pull from pillars or minion config: key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs ''' +# Import Python Libs +from __future__ import absolute_import + +# Import Salt Libs +from salt.utils import SaltInvocationError + def __virtual__(): ''' @@ -144,10 +150,15 @@ def present( if isinstance(value, list): value = ','.join(value) - record = __salt__['boto_route53.get_record'](name, zone, record_type, - False, region, key, keyid, - profile, split_dns, - private_zone) + try: + record = __salt__['boto_route53.get_record'](name, zone, record_type, + False, region, key, keyid, + profile, split_dns, + private_zone) + except SaltInvocationError as err: + ret['comment'] = 'Error: {0}'.format(err) + ret['result'] = False + return ret if isinstance(record, dict) and not record: if __opts__['test']: diff --git a/salt/states/cmd.py b/salt/states/cmd.py index c5ef8f97ae..266a184952 100644 --- a/salt/states/cmd.py +++ b/salt/states/cmd.py @@ -217,6 +217,7 @@ except ImportError: pass # Import salt libs +import salt.utils from salt.exceptions import CommandExecutionError, SaltRenderError from salt.ext.six import string_types @@ -494,7 +495,7 @@ def wait(name, # Alias "cmd.watch" to "cmd.wait", as this is a common misconfiguration -watch = wait +watch = salt.utils.alias_function(wait, 'watch') def wait_script(name, diff --git a/salt/states/dellchassis.py b/salt/states/dellchassis.py index 777500c951..227718c8ca 100644 --- a/salt/states/dellchassis.py +++ b/salt/states/dellchassis.py @@ -4,24 +4,131 @@ Manage chassis via Salt Proxies. .. versionadded:: 2015.8.2 -Example managing a Dell chassis: +Below is an example state that sets parameters just to show the basics. .. code-block:: yaml my-dell-chassis: - chassis.dell: - - name: my-dell-chassis + dellchassis.chassis: + - chassis_name: my-dell-chassis + - datacenter: dc-1-us - location: my-location - mode: 2 - idrac_launch: 1 - slot_names: - - 1: my-slot-name - - 2: my-other-slot-name + - server-1: my-slot-name + - server-2: my-other-slot-name - blade_power_states: - server-1: on - server-2: off - server-3: powercycle +However, it is possible to place the entire set of chassis configuration +data in pillar. Here's an example pillar +structure: + +.. code-block:: yaml + + proxy: + host: 10.27.20.18 + admin_username: root + admin_password: saltstack + proxytype: fx2 + + chassis: + name: fx2-1 + username: root + password: saltstack1 + datacenter: london + location: rack-1-shelf-3 + management_mode: 2 + idrac_launch: 0 + slot_names: + - 'server-1': blade1 + - 'server-2': blade2 + + blades: + blade1: + idrac_password: saltstack1 + ipmi_over_lan: True + ip: 172.17.17.1 + subnet: 255.255.0.0 + netmask: 172.17.255.255 + blade2: + idrac_password: saltstack1 + ipmi_over_lan: True + ip: 172.17.17.2 + subnet: 255.255.0.0 + netmask: 172.17.255.255 + blade3: + idrac_password: saltstack1 + ipmi_over_lan: True + ip: 172.17.17.2 + subnet: 255.255.0.0 + netmask: 172.17.255.255 + blade4: + idrac_password: saltstack1 + ipmi_over_lan: True + ip: 172.17.17.2 + subnet: 255.255.0.0 + netmask: 172.17.255.255 + + switches: + switch-1: + ip: 192.168.1.2 + netmask: 255.255.255.0 + broadcast: 192.168.1.255 + snmp: nonpublic + password: saltstack1 + switch-2: + ip: 192.168.1.3 + netmask: 255.255.255.0 + broadcast: 192.168.1.255 + snmp: nonpublic + password: saltstack1 + +And to go with it, here's an example state that pulls the data from pillar + +.. code-block:: yaml + + {% set details = pillar['chassis'] with context %} + standup-step1: + dellchassis.chassis: + - name: {{ details['name'] }} + - location: {{ details['location'] }} + - mode: {{ details['management_mode'] }} + - idrac_launch: {{ details['idrac_launch'] }} + - slot_names + {% for k, v in details['chassis']['slot_names'].iteritems() %} + - {{ k }}: {{ v }} + {% endfor %} + + + {% for k, v in details['chassis']['switches'].iteritems() %} + standup-switches-{{ k }}: + dellchassis.dell_switch: + - name: {{ k }} + - ip: {{ v['ip'] }} + - netmask: {{ v['netmask'] }} + - gateway: {{ v['gateway'] }} + - password: {{ v['password'] }} + - snmp: {{ v['snmp'] }} + {% endfor %} + + dellchassis + {% for k, v in details['chassis']['slot_names'].iteritems() %} + + - {{ k }}: {{ v }} + {% endfor %} + + blade_powercycle: + chassis.dell_chassis: + - blade_power_states: + - server-1: powercycle + - server-2: powercycle + - server-3: powercycle + - server-4: powercycle + ''' # Import python libs @@ -35,13 +142,13 @@ def __virtual__(): return 'chassis.cmd' in __salt__ -def blade_idrac(name, idrac_password=None, idrac_ipmi=None, +def blade_idrac(idrac_password=None, idrac_ipmi=None, idrac_ip=None, idrac_netmask=None, idrac_gateway=None, + idrac_dnsname=None, drac_dhcp=None): ''' Set parameters for iDRAC in a blade. - :param name: The name of the blade to address :param idrac_password: Password to establish for the iDRAC interface :param idrac_ipmi: Enable/Disable IPMI over LAN :param idrac_ip: Set IP address for iDRAC @@ -52,20 +159,58 @@ def blade_idrac(name, idrac_password=None, idrac_ipmi=None, :return: A standard Salt changes dictionary ''' - pass + ret = {'result': True, + 'changes': {}, + 'comment': ''} + + if not idrac_password: + password = __pillar__['proxy']['admin_password'] + else: + password = idrac_password + + if idrac_ipmi: + if idrac_ipmi is True: + idrac_ipmi = '1' + if idrac_ipmi is False: + idrac_ipmi = '0' + current_ipmi = __salt__['dracr.get_general']('cfgIpmiLan', 'cfgIpmiLanEnable', + host=idrac_ip, admin_username='root', + admin_password=password) + + if current_ipmi != idrac_ipmi: + ch = {'Old': current_ipmi, 'New': idrac_ipmi} + ret['changes']['IPMI'] = ch + + if idrac_dnsname: + dnsret = __salt__['dracr.get_dns_dracname'](host=idrac_ip, admin_username='root', + admin_password=password) + current_dnsname = dnsret['Key=iDRAC.Embedded.1#NIC.1']['DNSRacName'] + if current_dnsname != idrac_dnsname: + ch = {'Old': current_dnsname, + 'New': idrac_dnsname} + ret['changes']['DNSRacName'] = ch + + return ret -def chassis(name, location=None, mode=None, idrac_launch=None, slot_names=None, - blade_power_states=None): +def chassis(name, chassis_name=None, password=None, datacenter=None, + location=None, mode=None, idrac_launch=None, slot_names=None, + blade_power_states=None): ''' Manage a Dell Chassis. - name + chassis_name The name of the chassis. + datacenter + The datacenter in which the chassis is located + location The location of the chassis. + password + Password for the chassis + mode The management mode of the chassis. Viable options are: @@ -96,9 +241,10 @@ def chassis(name, location=None, mode=None, idrac_launch=None, slot_names=None, .. code-block:: yaml my-dell-chassis: - chassis.dell: + dellchassis.chassis: - name: my-dell-chassis - location: my-location + - datacenter: london - mode: 2 - idrac_launch: 1 - slot_names: @@ -109,7 +255,7 @@ def chassis(name, location=None, mode=None, idrac_launch=None, slot_names=None, - server-2: off - server-3: powercycle ''' - ret = {'name': name, + ret = {'chassis_name': chassis_name, 'result': True, 'changes': {}, 'comment': ''} @@ -119,12 +265,27 @@ def chassis(name, location=None, mode=None, idrac_launch=None, slot_names=None, mode_cmd = 'cfgRacTuneChassisMgmtAtServer' launch_cmd = 'cfgRacTuneIdracDNSLaunchEnable' + inventory = __salt__[chassis_cmd]('inventory') + + if idrac_launch: + idrac_launch = str(idrac_launch) + current_name = __salt__[chassis_cmd]('get_chassis_name') - if name != current_name: + if chassis_name != current_name: ret['changes'].update({'Name': {'Old': current_name, - 'New': name}}) + 'New': chassis_name}}) + current_dc = __salt__[chassis_cmd]('get_chassis_datacenter') + if datacenter and datacenter != current_dc: + ret['changes'].update({'Datacenter': + {'Old': current_dc, + 'New': datacenter}}) + + if password: + ret['changes'].update({'Password': + {'Old': '******', + 'New': '******'}}) if location: current_location = __salt__[chassis_cmd]('get_chassis_location') if location != current_location: @@ -147,11 +308,16 @@ def chassis(name, location=None, mode=None, idrac_launch=None, slot_names=None, if slot_names: current_slot_names = __salt__[chassis_cmd]('list_slotnames') - for key, val in slot_names: + for s in slot_names: + key = s.keys()[0] + new_name = s[key] + if key.startswith('slot-'): + key = key[5:] + current_slot_name = current_slot_names.get(key).get('slotname') - if current_slot_name != val['name']: + if current_slot_name != new_name: old = {key: current_slot_name} - new = {key: val} + new = {key: new_name} if ret['changes'].get('Slot Names') is None: ret['changes'].update({'Slot Names': {'Old': {}, @@ -159,23 +325,33 @@ def chassis(name, location=None, mode=None, idrac_launch=None, slot_names=None, ret['changes']['Slot Names']['Old'].update(old) ret['changes']['Slot Names']['New'].update(new) - # TODO: Refactor this and make DRY - can probable farm this out to a new funciton + current_power_states = {} + target_power_states = {} if blade_power_states: - # TODO: Get the power state list working - current_power_states = 'get a list of current power states' - for key, val in blade_power_states: - # TODO: Get the correct state infos - current_power_state = current_power_states.get(key).get('state') - # TODO: Don't just compare values, check if True should be "on" or "off" etc - if current_power_state != val: - old = {key: current_power_state} - new = {key: val} - if ret['changes'].get('Blade Power States') is None: - ret['changes'].update({'Blade Power States': - {'Old': {}, - 'New': {}}}) - ret['changes']['Blade Power States']['Old'].update(old) - ret['changes']['Blade Power States']['New'].update(new) + for b in blade_power_states: + key = b.keys()[0] + status = __salt__[chassis_cmd]('server_powerstatus', module=key) + current_power_states[key] = status.get('status', -1) + if b[key] == 'powerdown': + if current_power_states[key] != -1 and current_power_states[key]: + target_power_states[key] = 'powerdown' + if b[key] == 'powerup': + if current_power_states[key] != -1 and not current_power_states[key]: + target_power_states[key] = 'powerup' + if b[key] == 'powercycle': + if current_power_states[key] != -1 and not current_power_states[key]: + target_power_states[key] = 'powerup' + if current_power_states[key] != -1 and current_power_states[key]: + target_power_states[key] = 'powercycle' + for k, v in target_power_states.iteritems(): + old = {k: current_power_states[k]} + new = {k: v} + if ret['changes'].get('Blade Power States') is None: + ret['changes'].update({'Blade Power States': + {'Old': {}, + 'New': {}}}) + ret['changes']['Blade Power States']['Old'].update(old) + ret['changes']['Blade Power States']['New'].update(new) if ret['changes'] == {}: ret['comment'] = 'Dell chassis is already in the desired state.' @@ -190,21 +366,50 @@ def chassis(name, location=None, mode=None, idrac_launch=None, slot_names=None, name = __salt__[chassis_cmd]('set_chassis_name', name) if location: location = __salt__[chassis_cmd]('set_chassis_location', location) + pw_result = True + if password: + pw_single = True + if __salt__[chassis_cmd]('change_password', username='root', uid=1, + password=password): + for blade in inventory['server'].keys(): + pw_single = __salt__[chassis_cmd]('deploy_password', + username='root', + password=password, + module=blade) + if not pw_single: + pw_result = False + else: + pw_result = False + + if datacenter: + datacenter_result = __salt__[chassis_cmd]('set_chassis_datacenter', + datacenter) if mode: mode = __salt__[chassis_cmd]('set_general', cfg_tuning, mode_cmd, mode) if idrac_launch: idrac_launch = __salt__[chassis_cmd]('set_general', cfg_tuning, launch_cmd, idrac_launch) - if slot_names: + if ret['changes'].get('Slot Names') is not None: slot_rets = [] - for key, val in slot_names.iteritems(): - slot_name = val.get('slotname') - slot_rets.append(__salt__[chassis_cmd]('set_slotname', key, slot_name)) + for s in slot_names: + key = s.keys()[0] + new_name = s[key] + if key.startswith('slot-'): + key = key[5:] + slot_rets.append(__salt__[chassis_cmd]('set_slotname', key, new_name)) + if any(slot_rets) is False: slot_names = False else: slot_names = True - if any([name, location, mode, idrac_launch, slot_names]) is False: + powerchange_all_ok = True + for k, v in target_power_states.iteritems(): + powerchange_ok = __salt__[chassis_cmd]('server_power', v, module=k) + if not powerchange_ok: + powerchange_all_ok = False + + if any([name, location, mode, idrac_launch, + slot_names, powerchange_all_ok]) is False: ret['result'] = False ret['comment'] = 'There was an error setting the Dell chassis.' @@ -212,7 +417,7 @@ def chassis(name, location=None, mode=None, idrac_launch=None, slot_names=None, return ret -def dell_switch(name, ip=None, netmask=None, gateway=None, dhcp=None, +def switch(name, ip=None, netmask=None, gateway=None, dhcp=None, password=None, snmp=None): ''' Manage switches in a Dell Chassis. @@ -245,7 +450,7 @@ def dell_switch(name, ip=None, netmask=None, gateway=None, dhcp=None, .. code-block:: yaml my-dell-chassis: - chassis.dell_switch: + dellchassis.dell_switch: - switch: switch-1 - ip: 192.168.1.1 - netmask: 255.255.255.0 diff --git a/salt/states/event.py b/salt/states/event.py index 86dac90ab8..74861594c2 100644 --- a/salt/states/event.py +++ b/salt/states/event.py @@ -2,6 +2,10 @@ ''' Send events through Salt's event system during state runs ''' +from __future__ import absolute_import + +# import salt libs +import salt.utils def send(name, @@ -86,5 +90,5 @@ def wait(name, sfun=None): return {'name': name, 'changes': {}, 'result': True, 'comment': ''} -mod_watch = send -fire_master = send +mod_watch = salt.utils.alias_function(send, 'mod_watch') +fire_master = salt.utils.alias_function(send, 'fire_master') diff --git a/salt/states/file.py b/salt/states/file.py index fd2054a02f..a2f80ac003 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -2486,7 +2486,7 @@ def replace(name, pattern, repl, count=0, - flags=0, + flags=8, bufsize=1, append_if_not_found=False, prepend_if_not_found=False, @@ -2513,16 +2513,18 @@ def replace(name, replaced, otherwise all occurrences will be replaced. flags - A list of flags defined in the :ref:`re module documentation `. - Each list item should be a string that will correlate to the human-friendly flag name. - E.g., ``['IGNORECASE', 'MULTILINE']``. Note: multiline searches must specify ``file`` - as the ``bufsize`` argument below. Defaults to 0 and can be a list or an int. + A list of flags defined in the :ref:`re module documentation + `. Each list item should be a string that will + correlate to the human-friendly flag name. E.g., ``['IGNORECASE', + 'MULTILINE']``. Optionally, ``flags`` may be an int, with a value + corresponding to the XOR (``|``) of all the desired flags. Defaults to + 8 (which supports 'MULTILINE'). bufsize - How much of the file to buffer into memory at once. The default value ``1`` processes - one line at a time. The special value ``file`` may be specified which will read the - entire file into memory before processing. Note: multiline searches must specify ``file`` - buffering. Can be an int or a str. + How much of the file to buffer into memory at once. The default value + ``1`` processes one line at a time. The special value ``file`` may be + specified which will read the entire file into memory before + processing. append_if_not_found If pattern is not found and set to ``True`` then, the content will be appended to the file. @@ -4353,6 +4355,8 @@ def serialize(name, 'result': False } + contents += '\n' + if __opts__['test']: ret['changes'] = __salt__['file.check_managed_changes']( name=name, @@ -4622,10 +4626,18 @@ def mod_run_check_cmd(cmd, filename, **check_cmd_opts): log.debug('running our check_cmd') _cmd = '{0} {1}'.format(cmd, filename) - if __salt__['cmd.retcode'](_cmd, **check_cmd_opts) != 0: - return {'comment': 'check_cmd execution failed', - 'skip_watch': True, - 'result': False} + cret = __salt__['cmd.run_all'](_cmd, **check_cmd_opts) + if cret['retcode'] != 0: + ret = {'comment': 'check_cmd execution failed', + 'skip_watch': True, + 'result': False} + + if cret.get('stdout'): + ret['comment'] += '\n' + cret['stdout'] + if cret.get('stderr'): + ret['comment'] += '\n' + cret['stderr'] + + return ret # No reason to stop, return True return True diff --git a/salt/states/firewalld.py b/salt/states/firewalld.py index 3980db1849..b4a8dfc86f 100644 --- a/salt/states/firewalld.py +++ b/salt/states/firewalld.py @@ -11,15 +11,16 @@ masquerading, and allows ports 22/tcp and 25/tcp. .. code-block:: yaml public: - - name: public - - block_icmp - - echo-reply - - echo-request - - default: False - - masquerade: True - - ports: - - 22/tcp - - 25/tcp + firewalld.present: + - name: public + - block_icmp: + - echo-reply + - echo-request + - default: False + - masquerade: True + - ports: + - 22/tcp + - 25/tcp The following example applies changes to the public zone, enables masquerading and configures port forwarding TCP traffic from port 22 @@ -35,10 +36,14 @@ to 2222, and forwards TCP traffic from port 80 to 443 at 192.168.0.1. - 22:2222:tcp - 80:443:tcp:192.168.0.1 ''' -from __future__ import absolute_import +# Import Python Libs +from __future__ import absolute_import import logging -import salt.exceptions + +# Import Salt Libs +from salt.exceptions import CommandExecutionError +import salt.utils log = logging.getLogger(__name__) @@ -61,92 +66,119 @@ def present(name, port_fwd=None, services=None): ''' - Ensure a zone has specific attributes + Ensure a zone has specific attributes. ''' ret = {'name': name, - 'result': True, - 'changes': {'icmp_blocks': [], - 'ports': [], - 'port_fwd': [], - 'services': []}, - 'comment': {'icmp_blocks': [], - 'ports': [], - 'port_fwd': [], - 'services': []}} + 'result': False, + 'changes': {}, + 'comment': ''} - if name not in __salt__['firewalld.get_zones'](): - if __opts__['test']: - ret['comment'][name] = '`{0}` will be created'.format(name) - else: - __salt__['firewalld.new_zone'](name) - ret['changes'][name] = '`{0}` zone has been successfully created'.format(name) - else: - ret['comment'][name] = '`{0}` zone already exists'.format(name) + try: + zones = __salt__['firewalld.get_zones']() + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret + + if name not in zones: + if not __opts__['test']: + try: + __salt__['firewalld.new_zone'](name) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret + + ret['changes'].update({name: + {'old': zones, + 'new': name}}) if block_icmp: - _valid_icmp_types = __salt__['firewalld.get_icmp_types']() - _current_icmp_blocks = __salt__['firewalld.list_icmp_block'](name) + new_icmp_types = [] + try: + _valid_icmp_types = __salt__['firewalld.get_icmp_types']() + _current_icmp_blocks = __salt__['firewalld.list_icmp_block'](name) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret for icmp_type in set(block_icmp): if icmp_type in _valid_icmp_types: - if icmp_type in _current_icmp_blocks: - ret['comment']['icmp_blocks'].append( - '`{0}` already exists'.format(icmp_type) - ) - else: - if __opts__['test']: - ret['comment']['icmp_blocks'].append( - '`{0}` will be blocked'.format(icmp_type) - ) - else: - __salt__['firewalld.block_icmp'](name, icmp_type) - ret['changes']['icmp_blocks'].append( - '`{0}` has been blocked'.format(icmp_type) - ) + if icmp_type not in _current_icmp_blocks: + new_icmp_types.append(icmp_type) + if not __opts__['test']: + try: + __salt__['firewalld.block_icmp'](name, icmp_type) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret else: log.error('{0} is an invalid ICMP type'.format(icmp_type)) + if new_icmp_types: + ret['changes'].update({'icmp_blocks': + {'old': _current_icmp_blocks, + 'new': new_icmp_types}}) if default: - if __salt__['firewalld.default_zone']() == name: - ret['comment']['default'] = '`{0}` is already the default zone'.format(name) - else: - if __opts__['test']: - ret['comment']['default'] = '`{0}` wll be set to the default zone'.format(name) - else: - __salt__['firewalld.set_default_zone'](name) - ret['changes']['default'] = '`{0}` has been set to the default zone'.format(name) + try: + default_zone = __salt__['firewalld.default_zone']() + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret + if name != default_zone: + if not __opts__['test']: + try: + __salt__['firewalld.set_default_zone'](name) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret + ret['changes'].update({'default': + {'old': default_zone, + 'new': name}}) if masquerade: - if __salt__['firewalld.get_masquerade'](name): - ret['comment']['masquerade'] = 'masquerading is already enabed' - else: - if __opts__['test']: - ret['comment']['masquerade'] = 'masquerading will be enabled' - else: - __salt__['firewalld.add_masquerade'](name) - ret['changes']['masquerade'] = 'masquerading successfully set' + try: + masquerade_ret = __salt__['firewalld.get_masquerade'](name) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret + if not masquerade_ret: + if not __opts__['test']: + try: + __salt__['firewalld.add_masquerade'](name) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret + ret['changes'].update({'masquerade': + {'old': '', + 'new': 'Masquerading successfully set.'}}) if ports: - _current_ports = __salt__['firewalld.list_ports'](name) - + new_ports = [] + try: + _current_ports = __salt__['firewalld.list_ports'](name) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret for port in ports: - if port in _current_ports: - ret['comment']['ports'].append( - '`{0}` already exists'.format(port) - ) - else: - if __opts__['test']: - ret['comment']['ports'].append( - '{0} will be added'.format(port) - ) - else: - __salt__['firewalld.add_port'](name, port) - ret['changes']['ports'].append( - '`{0}` has been added to the firewall'.format(port) - ) + if port not in _current_ports: + new_ports.append(port) + if not __opts__['test']: + try: + __salt__['firewalld.add_port'](name, port) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret + if new_ports: + ret['changes'].update({'ports': + {'old': _current_ports, + 'new': new_ports}}) if port_fwd: - _current_port_fwd = __salt__['firewalld.list_port_fwd'](name) + new_port_fwds = [] + try: + _current_port_fwd = __salt__['firewalld.list_port_fwd'](name) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret for port in port_fwd: dstaddr = '' @@ -157,45 +189,55 @@ def present(name, else: (src, dest, protocol) = port.split(':') - for i in _current_port_fwd: - if (src == i['Source port'] and dest == i['Destination port'] and - protocol == i['Protocol'] and dstaddr == i['Destination address']): + for item in _current_port_fwd: + if (src == item['Source port'] and dest == item['Destination port'] and + protocol == item['Protocol'] and dstaddr == item['Destination address']): rule_exists = True - if rule_exists: - ret['comment']['port_fwd'].append( - '`{0}` port forwarding already exists'.format(port) - ) - else: - if __opts__['test']: - ret['comment']['port_fwd'].append( - '`{0}` port will be added'.format(port) - ) - else: - __salt__['firewalld.add_port_fwd']( - name, src, dest, protocol, dstaddr - ) - ret['changes']['port_fwd'].append( - '`{0}` port forwarding has been added'.format(port) - ) + if rule_exists is False: + new_port_fwds.append(port) + if not __opts__['test']: + try: + __salt__['firewalld.add_port_fwd'](name, src, dest, protocol, dstaddr) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret + + if new_port_fwds: + ret['changes'].update({'port_fwd': + {'old': _current_port_fwd, + 'new': new_port_fwds}}) if services: - _current_services = __salt__['firewalld.list_services'](name) - + new_services = [] + try: + _current_services = __salt__['firewalld.list_services'](name) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret for service in services: - if service in _current_services: - ret['comment']['services'].append( - '`{0}` service already exists'.format(service) - ) - else: - if __opts__['test']: - ret['comment']['services'].append( - '`{0}` service will be added'.format(service) - ) - else: - __salt__['firewalld.new_service'](service) - ret['changes']['services'].append( - '`{0}` has been successfully added'.format(service) - ) + if service not in _current_services: + new_services.append(service) + if not __opts__['test']: + try: + __salt__['firewalld.add_service'](service, zone=name) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret + if new_services: + ret['changes'].update({'services': + {'old': _current_services, + 'new': new_services}}) + ret['result'] = True + if ret['changes'] == {}: + ret['comment'] = '\'{0}\' is already in the desired state.'.format(name) + return ret + + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Configuration for \'{0}\' will change.'.format(name) + return ret + + ret['comment'] = '\'{0}\' was configured.'.format(name) return ret diff --git a/salt/states/iptables.py b/salt/states/iptables.py index 9156165913..c59c61ee1d 100644 --- a/salt/states/iptables.py +++ b/salt/states/iptables.py @@ -373,6 +373,7 @@ def append(name, table='filter', family='ipv4', **kwargs): if ignore in kwargs: del kwargs[ignore] kwargs['name'] = name + kwargs['table'] = table rule = __salt__['iptables.build_rule'](family=family, **kwargs) command = __salt__['iptables.build_rule'](full='True', family=family, command='A', **kwargs) if __salt__['iptables.check'](table, @@ -500,6 +501,7 @@ def insert(name, table='filter', family='ipv4', **kwargs): if ignore in kwargs: del kwargs[ignore] kwargs['name'] = name + kwargs['table'] = table rule = __salt__['iptables.build_rule'](family=family, **kwargs) command = __salt__['iptables.build_rule'](full=True, family=family, command='I', **kwargs) if __salt__['iptables.check'](table, @@ -622,6 +624,7 @@ def delete(name, table='filter', family='ipv4', **kwargs): if ignore in kwargs: del kwargs[ignore] kwargs['name'] = name + kwargs['table'] = table rule = __salt__['iptables.build_rule'](family=family, **kwargs) command = __salt__['iptables.build_rule'](full=True, family=family, command='D', **kwargs) diff --git a/salt/states/module.py b/salt/states/module.py index 07e556b1fb..639bbc398d 100644 --- a/salt/states/module.py +++ b/salt/states/module.py @@ -127,7 +127,7 @@ def wait(name, **kwargs): 'comment': ''} # Alias module.watch to module.wait -watch = wait +watch = salt.utils.alias_function(wait, 'watch') def run(name, **kwargs): @@ -269,4 +269,4 @@ def run(name, **kwargs): ret['result'] = False return ret -mod_watch = run # pylint: disable=C0103 +mod_watch = salt.utils.alias_function(run, 'mod_watch') diff --git a/salt/states/pkgbuild.py b/salt/states/pkgbuild.py index 7a94d0f15e..b06cefa805 100644 --- a/salt/states/pkgbuild.py +++ b/salt/states/pkgbuild.py @@ -46,6 +46,9 @@ automatically from __future__ import absolute_import, print_function import os +# Import salt libs +import salt.utils + def built( name, @@ -53,12 +56,13 @@ def built( dest_dir, spec, sources, - template, tgt, + template=None, deps=None, env=None, results=None, - always=False, + force=False, + always=None, saltenv='base'): ''' Ensure that the named package is built and exists in the named directory @@ -78,12 +82,16 @@ def built( sources The list of package sources - template - Set to run the spec file through a templating engine - tgt The target platform to run the build on + template + Run the spec file through a templating engine + + .. versionchanged:: 2015.8.2 + This argument is now optional, allowing for no templating engine to + be used if none is desired. + deps Packages required to ensure that the named package is built can be hosted on either the salt master server or on an HTTP @@ -112,9 +120,20 @@ def built( results The names of the expected rpms that will be built + force : False + If ``True``, packages will be built even if they already exist in the + ``dest_dir``. This is useful when building a package for continuous or + nightly package builds. + + .. versionadded:: 2015.8.2 + always - Build with every run (good if the package is for continuous or - nightly package builds) + If ``True``, packages will be built even if they already exist in the + ``dest_dir``. This is useful when building a package for continuous or + nightly package builds. + + .. deprecated:: 2015.8.2 + Use ``force`` instead. saltenv The saltenv to use for files downloaded from the salt filesever @@ -123,7 +142,16 @@ def built( 'changes': {}, 'comment': '', 'result': True} - if not always: + + if always is not None: + salt.utils.warn_until( + 'Carbon', + 'The \'always\' argument to the pkgbuild.built state has been ' + 'deprecated, please use \'force\' instead.' + ) + force = always + + if not force: if isinstance(results, str): results = results.split(',') results = set(results) diff --git a/salt/states/postgres_tablespace.py b/salt/states/postgres_tablespace.py index db8e85e517..33545bdc71 100644 --- a/salt/states/postgres_tablespace.py +++ b/salt/states/postgres_tablespace.py @@ -2,10 +2,8 @@ ''' Management of PostgreSQL tablespace =================================== -The postgres_tablespace module is used to create and manage Postgres -tablespaces. -Tablespaces can be set as either absent or present. +A module used to create and manage PostgreSQL tablespaces. .. code-block:: yaml ssd-tablespace: diff --git a/salt/states/rabbitmq_cluster.py b/salt/states/rabbitmq_cluster.py index 36167c2b3b..d5eb6b8930 100644 --- a/salt/states/rabbitmq_cluster.py +++ b/salt/states/rabbitmq_cluster.py @@ -74,4 +74,4 @@ def joined(name, host, user='rabbit', ram_node=None, runas='root'): # Alias join to preserve backward compat -join = joined +join = salt.utils.alias_function(joined, 'join') diff --git a/salt/states/rabbitmq_plugin.py b/salt/states/rabbitmq_plugin.py index c05b7b3ca0..d3fff9716f 100644 --- a/salt/states/rabbitmq_plugin.py +++ b/salt/states/rabbitmq_plugin.py @@ -12,11 +12,14 @@ Example: some_plugin: rabbitmq_plugin.enabled: [] ''' -from __future__ import absolute_import -# Import python libs +# Import Python Libs +from __future__ import absolute_import import logging +# Import Salt Libs +from salt.exceptions import CommandExecutionError + log = logging.getLogger(__name__) @@ -40,25 +43,33 @@ def enabled(name, runas=None): ''' ret = {'name': name, 'result': True, 'comment': '', 'changes': {}} - result = {} - if __salt__['rabbitmq.plugin_is_enabled'](name, runas=runas): - ret['comment'] = 'Plugin {0} is already enabled'.format(name) + try: + plugin_enabled = __salt__['rabbitmq.plugin_is_enabled'](name, runas=runas) + except CommandExecutionError as err: + ret['result'] = False + ret['comment'] = 'Error: {0}'.format(err) return ret - if __opts__['test']: + if plugin_enabled: + ret['comment'] = 'Plugin \'{0}\' is already enabled.'.format(name) + return ret + + if not __opts__['test']: + try: + __salt__['rabbitmq.enable_plugin'](name, runas=runas) + except CommandExecutionError as err: + ret['result'] = False + ret['comment'] = 'Error: {0}'.format(err) + return ret + ret['changes'].update({'old': '', 'new': name}) + + if __opts__['test'] and ret['changes']: ret['result'] = None - ret['comment'] = 'Plugin {0} is set to be enabled'.format(name) - else: - result = __salt__['rabbitmq.enable_plugin'](name, runas=runas) - - if 'Error' in result: - ret['result'] = False - ret['comment'] = result['Error'] - elif 'Enabled' in result: - ret['comment'] = result['Enabled'] - ret['changes'] = {'old': '', 'new': name} + ret['comment'] = 'Plugin \'{0}\' is set to be enabled.'.format(name) + return ret + ret['comment'] = 'Plugin \'{0}\' was enabled.'.format(name) return ret @@ -73,23 +84,31 @@ def disabled(name, runas=None): ''' ret = {'name': name, 'result': True, 'comment': '', 'changes': {}} - result = {} - if not __salt__['rabbitmq.plugin_is_enabled'](name, runas=runas): - ret['comment'] = 'Plugin {0} is not enabled'.format(name) + try: + plugin_enabled = __salt__['rabbitmq.plugin_is_enabled'](name, runas=runas) + except CommandExecutionError as err: + ret['result'] = False + ret['comment'] = 'Error: {0}'.format(err) return ret - if __opts__['test']: + if not plugin_enabled: + ret['comment'] = 'Plugin \'{0}\' is already disabled.'.format(name) + return ret + + if not __opts__['test']: + try: + __salt__['rabbitmq.disable_plugin'](name, runas=runas) + except CommandExecutionError as err: + ret['result'] = False + ret['comment'] = 'Error: {0}'.format(err) + return ret + ret['changes'].update({'old': name, 'new': ''}) + + if __opts__['test'] and ret['changes']: ret['result'] = None - ret['comment'] = 'Plugin {0} is set to be disabled'.format(name) - else: - result = __salt__['rabbitmq.disable_plugin'](name, runas=runas) - - if 'Error' in result: - ret['result'] = False - ret['comment'] = result['Error'] - elif 'Disabled' in result: - ret['comment'] = result['Disabled'] - ret['changes'] = {'new': '', 'old': name} + ret['comment'] = 'Plugin \'{0}\' is set to be disabled.'.format(name) + return ret + ret['comment'] = 'Plugin \'{0}\' was disabled.'.format(name) return ret diff --git a/salt/states/rabbitmq_user.py b/salt/states/rabbitmq_user.py index d68a52a416..04ea0cfbea 100644 --- a/salt/states/rabbitmq_user.py +++ b/salt/states/rabbitmq_user.py @@ -8,18 +8,18 @@ Example: .. code-block:: yaml rabbit_user: - rabbitmq_user.present: - - password: password - - force: True - - tags: - - monitoring - - user - - perms: - - '/': - - '.*' - - '.*' - - '.*' - - runas: rabbitmq + rabbitmq_user.present: + - password: password + - force: True + - tags: + - monitoring + - user + - perms: + - '/': + - '.*' + - '.*' + - '.*' + - runas: rabbitmq ''' # Import python libs @@ -28,9 +28,8 @@ import logging # Import salt libs import salt.utils - -# Import 3rd-party libs import salt.ext.six as six +from salt.exceptions import CommandExecutionError log = logging.getLogger(__name__) @@ -42,20 +41,25 @@ def __virtual__(): return salt.utils.which('rabbitmqctl') is not None -def _check_perms_changes(name, newperms, runas=None): +def _check_perms_changes(name, newperms, runas=None, existing=None): ''' - Whether Rabbitmq user's permissions need to be changed + Check whether Rabbitmq user's permissions need to be changed. ''' if not newperms: return False - existing_perms = __salt__['rabbitmq.list_user_permissions'](name, runas=runas) + if existing is None: + try: + existing = __salt__['rabbitmq.list_user_permissions'](name, runas=runas) + except CommandExecutionError as err: + log.error('Error: {0}'.format(err)) + return False perm_need_change = False for vhost_perms in newperms: for vhost, perms in vhost_perms.iteritems(): - if vhost in existing_perms: - if perms != existing_perms[vhost]: + if vhost in existing: + if perms != existing[vhost]: perm_need_change = True else: perm_need_change = True @@ -63,14 +67,19 @@ def _check_perms_changes(name, newperms, runas=None): return perm_need_change -def _check_tags_changes(name, newtags, runas=None): +def _check_tags_changes(name, new_tags, runas=None): ''' Whether Rabbitmq user's tags need to be changed ''' - if newtags: - if isinstance(newtags, str): - newtags = newtags.split() - return __salt__['rabbitmq.list_users'](runas=runas)[name] - set(newtags) + if new_tags: + if isinstance(new_tags, str): + new_tags = new_tags.split() + try: + users = __salt__['rabbitmq.list_users'](runas=runas)[name] - set(new_tags) + except CommandExecutionError as err: + log.error('Error: {0}'.format(err)) + return [] + return users else: return [] @@ -97,97 +106,109 @@ def present(name, runas Name of the user to run the command ''' - - ret = {'name': name, 'result': True, 'comment': '', 'changes': {}} - result = {} - - user_exists = __salt__['rabbitmq.user_exists'](name, runas=runas) - - if user_exists and not any((force, perms, tags)): - log.debug('RabbitMQ user %s exists, ' - 'and force is not set.', name) - ret['comment'] = 'User {0} already presents'.format(name) + ret = {'name': name, 'result': False, 'comment': '', 'changes': {}} + try: + user = __salt__['rabbitmq.user_exists'](name, runas=runas) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) return ret + + if user and not any((force, perms, tags)): + log.debug('RabbitMQ user \'{0}\' exists and force is not set.'.format(name)) + ret['comment'] = 'User \'{0}\' is already present.'.format(name) + ret['result'] = True + return ret + + if not user: + ret['changes'].update({'user': + {'old': '', + 'new': name}}) + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'User \'{0}\' is set to be created.'.format(name) + return ret + + log.debug('RabbitMQ user \'{0}\' doesn\'t exist - Creating.'.format(name)) + try: + __salt__['rabbitmq.add_user'](name, password, runas=runas) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret else: - changes = {'old': '', 'new': ''} - - if not user_exists: - if __opts__['test']: - ret['result'] = None - ret['comment'] = 'User {0} is set to be created'.format(name) - return ret - - log.debug("RabbitMQ user %s doesn't exist - Creating", name) - result = __salt__['rabbitmq.add_user']( - name, password, runas=runas) - else: - log.debug('RabbitMQ user %s exists', name) - if force: - if __opts__['test']: - ret['result'] = None - - if password is not None: - if __opts__['test']: - ret['comment'] = ('User {0}\'s password is ' - 'set to be updated'.format(name)) + log.debug('RabbitMQ user \'{0}\' exists'.format(name)) + if force: + if password is not None: + if not __opts__['test']: + try: + __salt__['rabbitmq.change_password'](name, password, runas=runas) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) return ret - - result = __salt__['rabbitmq.change_password']( - name, password, runas=runas) - changes['new'] = 'Set password.\n' - else: - log.debug('Password for %s is not set - Clearing password', - name) - if __opts__['test']: - ret['comment'] = ('User {0}\'s password is ' - 'set to be removed'.format(name)) + ret['changes'].update({'password': + {'old': '', + 'new': 'Set password.'}}) + else: + if not __opts__['test']: + log.debug('Password for {0} is not set - Clearing password.'.format(name)) + try: + __salt__['rabbitmq.clear_password'](name, runas=runas) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) return ret + ret['changes'].update({'password': + {'old': 'Removed password.', + 'new': ''}}) - result = __salt__['rabbitmq.clear_password']( - name, runas=runas) - changes['old'] += 'Removed password.\n' - - if _check_tags_changes(name, tags, runas=runas): - if __opts__['test']: - ret['result'] = None - ret['comment'] += ('Tags for user {0} ' - 'is set to be changed'.format(name)) + new_tags = _check_tags_changes(name, tags, runas=runas) + if new_tags: + if not __opts__['test']: + try: + __salt__['rabbitmq.set_user_tags'](name, tags, runas=runas) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) return ret - result.update(__salt__['rabbitmq.set_user_tags']( - name, tags, runas=runas) - ) - changes['new'] += 'Set tags: {0}\n'.format(tags) - - if _check_perms_changes(name, perms, runas=runas): - if __opts__['test']: - ret['result'] = None - ret['comment'] += ('Permissions for user {0} ' - 'is set to be changed'.format(name)) - return ret - for vhost_perm in perms: - for vhost, perm in six.iteritems(vhost_perm): - result.update(__salt__['rabbitmq.set_permissions']( - vhost, name, perm[0], perm[1], perm[2], runas=runas) - ) - changes['new'] += ( - 'Set permissions {0} for vhost {1}' - ).format(perm, vhost) - - if 'Error' in result: - ret['result'] = False - ret['comment'] = result['Error'] - elif 'Added' in result: - ret['comment'] = result['Added'] - ret['changes'] = changes - elif 'Password Changed' in result: - ret['comment'] = result['Password Changed'] - ret['changes'] = changes - elif 'Password Cleared' in result: - ret['comment'] = result['Password Cleared'] - ret['changes'] = changes - + ret['changes'].update({'tags': + {'old': tags, + 'new': new_tags}}) + try: + existing_perms = __salt__['rabbitmq.list_user_permissions'](name, runas=runas)[0] + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) return ret + if _check_perms_changes(name, perms, runas=runas, existing=existing_perms): + for vhost_perm in perms: + for vhost, perm in six.iteritems(vhost_perm): + if not __opts__['test']: + try: + __salt__['rabbitmq.set_permissions']( + vhost, name, perm[0], perm[1], perm[2], runas=runas + ) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret + new_perms = {vhost: perm} + if existing_perms != new_perms: + if ret['changes'].get('perms') is None: + ret['changes'].update({'perms': + {'old': {}, + 'new': {}}}) + ret['changes']['perms']['old'].update(existing_perms) + ret['changes']['perms']['new'].update(new_perms) + + ret['result'] = True + if ret['changes'] == {}: + ret['comment'] = '\'{0}\' is already in the desired state.'.format(name) + return ret + + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Configuration for \'{0}\' will change.'.format(name) + return ret + + ret['comment'] = '\'{0}\' was configured.'.format(name) + return ret + def absent(name, runas=None): @@ -199,23 +220,34 @@ def absent(name, runas User to run the command ''' - ret = {'name': name, 'result': True, 'comment': '', 'changes': {}} + ret = {'name': name, 'result': False, 'comment': '', 'changes': {}} - user_exists = __salt__['rabbitmq.user_exists'](name, runas=runas) + try: + user_exists = __salt__['rabbitmq.user_exists'](name, runas=runas) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret - if not user_exists: - ret['comment'] = 'User {0} is not present'.format(name) - elif __opts__['test']: - ret['result'] = None - if user_exists: - ret['comment'] = 'Removing user {0}'.format(name) + if user_exists: + if not __opts__['test']: + try: + __salt__['rabbitmq.delete_user'](name, runas=runas) + except CommandExecutionError as err: + ret['comment'] = 'Error: {0}'.format(err) + return ret + ret['changes'].update({'name': + {'old': name, + 'new': ''}}) else: - result = __salt__['rabbitmq.delete_user'](name, runas=runas) - if 'Error' in result: - ret['result'] = False - ret['comment'] = result['Error'] - elif 'Deleted' in result: - ret['comment'] = 'Deleted' - ret['changes'] = {'new': '', 'old': name} + ret['result'] = True + ret['comment'] = 'The user \'{0}\' is not present.'.format(name) + return ret + if __opts__['test'] and ret['changes']: + ret['result'] = None + ret['comment'] = 'The user \'{0}\' will be removed.'.format(name) + return ret + + ret['result'] = True + ret['comment'] = 'The user \'{0}\' was removed.'.format(name) return ret diff --git a/salt/states/reg.py b/salt/states/reg.py index 712ab06030..c4f1f69864 100644 --- a/salt/states/reg.py +++ b/salt/states/reg.py @@ -94,56 +94,65 @@ def _parse_key(key): return hive, key -def present(name, value=None, vname=None, vdata=None, vtype='REG_SZ', reflection=True): +def present(name, + value=None, + vname=None, + vdata=None, + vtype='REG_SZ', + reflection=True, + use_32bit_registry=False): ''' Ensure a registry key or value is present. - :param str name: - A string value representing the full path of the key to include the - HIVE, Key, and all Subkeys. For example: + :param str name: A string value representing the full path of the key to + include the HIVE, Key, and all Subkeys. For example: - ``HKEY_LOCAL_MACHINE\\SOFTWARE\\Salt`` + ``HKEY_LOCAL_MACHINE\\SOFTWARE\\Salt`` - Valid hive values include: - - HKEY_CURRENT_USER or HKCU - - HKEY_LOCAL_MACHINE or HKLM - - HKEY_USERS or HKU + Valid hive values include: + - HKEY_CURRENT_USER or HKCU + - HKEY_LOCAL_MACHINE or HKLM + - HKEY_USERS or HKU - :param str value: - Deprecated. Use vname and vdata instead. Included here for backwards - compatability. + :param str value: Deprecated. Use vname and vdata instead. Included here for + backwards compatability. - :param str vname: - The name of the value you'd like to create beneath the Key. If this - parameter is not passed it will assume you want to set the (Default) - value + :param str vname: The name of the value you'd like to create beneath the + Key. If this parameter is not passed it will assume you want to set the + (Default) value - :param str vdata: - The value you'd like to set for the Key. If a value name (vname) is - passed, this will be the data for that value name. If not, this will be - the (Default) value for the key. + :param str vdata: The value you'd like to set for the Key. If a value name + (vname) is passed, this will be the data for that value name. If not, this + will be the (Default) value for the key. - The type for the (Default) value is always REG_SZ and cannot be changed. - This parameter is optional. If not passed, the Key will be created with. + The type for the (Default) value is always REG_SZ and cannot be changed. + This parameter is optional. If not passed, the Key will be created with no + associated item/value pairs. - :param str vtype: - The value type for the data you wish to store in the registry. Valid - values are: + :param str vtype: The value type for the data you wish to store in the + registry. Valid values are: - - REG_BINARY - - REG_DWORD - - REG_EXPAND_SZ - - REG_MULTI_SZ - - REG_SZ (Default) + - REG_BINARY + - REG_DWORD + - REG_EXPAND_SZ + - REG_MULTI_SZ + - REG_SZ (Default) - :param bool reflection: - On 64 bit machines a duplicate value will be created in the - ``Wow6432Node`` for 32bit programs. This only applies to the SOFTWARE - key. This option is ignored on 32bit operating systems. This value - defaults to True. Set it to False to disable reflection. + :param bool reflection: On 64 bit machines a duplicate value will be created + in the ``Wow6432Node`` for 32bit programs. This only applies to the SOFTWARE + key. This option is ignored on 32bit operating systems. This value defaults + to True. Set it to False to disable reflection. - :return: - Returns a dictionary showing the results of the registry operation. + .. deprecated:: 2015.8.2 + Use `use_32bit_registry` instead. + The parameter seems to have no effect since Windows 7 / Windows 2008R2 + removed support for reflection. The parameter will be removed in Boron. + + :param bool use_32bit_registry: Use the 32bit portion of the registry. + Applies only to 64bit windows. 32bit Windows will ignore this parameter. + Default if False. + + :return: Returns a dictionary showing the results of the registry operation. :rtype: dict The following example will set the ``(Default)`` value for the @@ -199,7 +208,10 @@ def present(name, value=None, vname=None, vdata=None, vtype='REG_SZ', reflection hive, key = _parse_key(name) # Determine what to do - reg_current = __salt__['reg.read_value'](hive, key, vname) + reg_current = __salt__['reg.read_value'](hive=hive, + key=key, + vname=vname, + use_32bit_registry=use_32bit_registry) if vdata == reg_current['vdata'] and reg_current['success']: ret['comment'] = '{0} in {1} is already configured'.\ @@ -217,8 +229,12 @@ def present(name, value=None, vname=None, vdata=None, vtype='REG_SZ', reflection return ret # Configure the value - ret['result'] = __salt__['reg.set_value'](hive, key, vname, vdata, vtype, - reflection) + ret['result'] = __salt__['reg.set_value'](hive=hive, + key=key, + vname=vname, + vdata=vdata, + vtype=vtype, + use_32bit_registry=use_32bit_registry) if not ret['result']: ret['changes'] = {} @@ -230,11 +246,33 @@ def present(name, value=None, vname=None, vdata=None, vtype='REG_SZ', reflection return ret -def absent(name, vname=None): +def absent(name, vname=None, use_32bit_registry=False): ''' Ensure a registry value is removed. To remove a key use key_absent. - Example: + :param str name: A string value representing the full path of the key to + include the HIVE, Key, and all Subkeys. For example: + + ``HKEY_LOCAL_MACHINE\\SOFTWARE\\Salt`` + + Valid hive values include: + + - HKEY_CURRENT_USER or HKCU + - HKEY_LOCAL_MACHINE or HKLM + - HKEY_USERS or HKU + + :param str vname: The name of the value you'd like to create beneath the + Key. If this parameter is not passed it will assume you want to set the + (Default) value + + :param bool use_32bit_registry: Use the 32bit portion of the registry. + Applies only to 64bit windows. 32bit Windows will ignore this parameter. + Default if False. + + :return: Returns a dictionary showing the results of the registry operation. + :rtype: dict + + CLI Example: .. code-block:: yaml @@ -242,9 +280,11 @@ def absent(name, vname=None): reg.absent In the above example the path is interpreted as follows: + - ``HKEY_CURRENT_USER`` is the hive - ``SOFTWARE\\Salt`` is the key - ``version`` is the value name + So the value ``version`` will be deleted from the ``SOFTWARE\\Salt`` key in the ``HKEY_CURRENT_USER`` hive. ''' @@ -256,11 +296,17 @@ def absent(name, vname=None): hive, key = _parse_key(name) # Determine what to do - reg_check = __salt__['reg.read_value'](hive, key, vname) + reg_check = __salt__['reg.read_value'](hive=hive, + key=key, + vname=vname, + use_32bit_registry=use_32bit_registry) if not reg_check['success'] or reg_check['vdata'] == '(value not set)': if not vname: hive, key, vname = _parse_key_value(name) - reg_check = __salt__['reg.read_value'](hive, key, vname) + reg_check = __salt__['reg.read_value'](hive=hive, + key=key, + vname=vname, + use_32bit_registry=use_32bit_registry) if not reg_check['success'] or reg_check['vdata'] == '(value not set)': ret['comment'] = '{0} is already absent'.format(name) return ret @@ -278,7 +324,10 @@ def absent(name, vname=None): return ret # Delete the value - ret['result'] = __salt__['reg.delete_value'](hive, key, vname) + ret['result'] = __salt__['reg.delete_value'](hive=hive, + key=key, + vname=vname, + use_32bit_registry=use_32bit_registry) if not ret['result']: ret['changes'] = {} ret['comment'] = r'Failed to remove {0} from {1}'.format(key, hive) @@ -289,39 +338,40 @@ def absent(name, vname=None): return ret -def key_absent(name, force=False): +def key_absent(name, force=False, use_32bit_registry=False): r''' .. versionadded:: 2015.5.4 Ensure a registry key is removed. This will remove a key and all value entries it contains. It will fail if the key contains subkeys. - :param str name: - A string representing the full path to the key to be removed to include - the hive and the keypath. The hive can be any of the following: - - HKEY_LOCAL_MACHINE or HKLM - - HKEY_CURRENT_USER or HKCU - - HKEY_USER or HKU + :param str name: A string representing the full path to the key to be + removed to include the hive and the keypath. The hive can be any of the following: - :param bool force: - A boolean value indicating that all subkeys should be deleted with the - key. If force=False and subkeys exists beneath the key you want to - delete, key_absent will fail. Use with caution. The default is False. + - HKEY_LOCAL_MACHINE or HKLM + - HKEY_CURRENT_USER or HKCU + - HKEY_USER or HKU - :return: - Returns a dictionary showing the results of the registry operation. + :param bool force: A boolean value indicating that all subkeys should be + deleted with the key. If force=False and subkeys exists beneath the key you + want to delete, key_absent will fail. Use with caution. The default is False. + + :return: Returns a dictionary showing the results of the registry operation. :rtype: dict The following example will delete the ``SOFTWARE\Salt`` key and all subkeys under the ``HKEY_CURRENT_USER`` hive. - Example:: + Example: + + .. codeblock:: yaml 'HKEY_CURRENT_USER\SOFTWARE\Salt': reg.key_absent: - force: True In the above example the path is interpreted as follows: + - ``HKEY_CURRENT_USER`` is the hive - ``SOFTWARE\Salt`` is the key ''' @@ -333,7 +383,9 @@ def key_absent(name, force=False): hive, key = _parse_key(name) # Determine what to do - if not __salt__['reg.read_value'](hive, key)['success']: + if not __salt__['reg.read_value'](hive=hive, + key=key, + use_32bit_registry=use_32bit_registry)['success']: ret['comment'] = '{0} is already absent'.format(name) return ret @@ -348,8 +400,13 @@ def key_absent(name, force=False): return ret # Delete the value - __salt__['reg.delete_key'](hive, key, force=force) - if __salt__['reg.read_value'](hive, key)['success']: + __salt__['reg.delete_key'](hive=hive, + key=key, + force=force, + use_32bit_registry=use_32bit_registry) + if __salt__['reg.read_value'](hive=hive, + key=key, + use_32bit_registry=use_32bit_registry)['success']: ret['result'] = False ret['changes'] = {} ret['comment'] = 'Failed to remove registry key {0}'.format(name) diff --git a/salt/states/schedule.py b/salt/states/schedule.py index a303550b8c..749f31a957 100644 --- a/salt/states/schedule.py +++ b/salt/states/schedule.py @@ -193,6 +193,12 @@ def present(name, ret['comment'] = new_item['comment'] return ret + # The schedule.list gives us an item that is guaranteed to have an + # 'enabled' argument. Before comparing, add 'enabled' if it's not + # available (assume True, like schedule.list does) + if 'enabled' not in new_item: + new_item['enabled'] = True + if new_item == current_schedule[name]: ret['comment'].append('Job {0} in correct state'.format(name)) else: diff --git a/salt/states/trafficserver.py b/salt/states/trafficserver.py index a0e70e7bfd..9588dda003 100644 --- a/salt/states/trafficserver.py +++ b/salt/states/trafficserver.py @@ -2,6 +2,7 @@ ''' Control Apache Traffic Server ============================= + .. versionadded:: 2015.8.0 ''' diff --git a/salt/states/virtualenv_mod.py b/salt/states/virtualenv_mod.py index 5e8ef89e02..ff8a2434ec 100644 --- a/salt/states/virtualenv_mod.py +++ b/salt/states/virtualenv_mod.py @@ -86,7 +86,8 @@ def managed(name, Pass `--upgrade` to `pip install`. - Also accepts any kwargs that the virtualenv module will. + Also accepts any kwargs that the virtualenv module will. + However, some kwargs require `- distribute: True` .. code-block:: yaml @@ -249,4 +250,4 @@ def managed(name, 'old': old if old else ''} return ret -manage = managed # pylint: disable=C0103 +manage = salt.utils.alias_function(managed, 'manage') diff --git a/salt/states/win_system.py b/salt/states/win_system.py index 78e7cbf665..33a9608d90 100644 --- a/salt/states/win_system.py +++ b/salt/states/win_system.py @@ -77,7 +77,7 @@ def computer_desc(name): '{0!r}'.format(name)) return ret -computer_description = computer_desc +computer_description = salt.utils.alias_function(computer_desc, 'computer_description') def computer_name(name): diff --git a/salt/transport/zeromq.py b/salt/transport/zeromq.py index 7478ad7dba..9ae8a37e7c 100644 --- a/salt/transport/zeromq.py +++ b/salt/transport/zeromq.py @@ -157,7 +157,11 @@ class AsyncZeroMQReqChannel(salt.transport.client.ReqChannel): # Return controle back to the caller, continue when authentication succeeds yield self.auth.authenticate() # Return control to the caller. When send() completes, resume by populating ret with the Future.result - ret = yield self.message_client.send(self._package_load(self.auth.crypticle.dumps(load)), timeout=timeout) + ret = yield self.message_client.send( + self._package_load(self.auth.crypticle.dumps(load)), + timeout=timeout, + tries=tries, + ) key = self.auth.get_keys() cipher = PKCS1_OAEP.new(key) aes = cipher.decrypt(ret['key']) @@ -182,9 +186,11 @@ class AsyncZeroMQReqChannel(salt.transport.client.ReqChannel): @tornado.gen.coroutine def _do_transfer(): # Yield control to the caller. When send() completes, resume by populating data with the Future.result - data = yield self.message_client.send(self._package_load(self.auth.crypticle.dumps(load)), - timeout=timeout, - ) + data = yield self.message_client.send( + self._package_load(self.auth.crypticle.dumps(load)), + timeout=timeout, + tries=tries, + ) # we may not have always data # as for example for saltcall ret submission, this is a blind # communication, we do not subscribe to return events, we just @@ -213,7 +219,12 @@ class AsyncZeroMQReqChannel(salt.transport.client.ReqChannel): :param int tries: The number of times to make before failure :param int timeout: The number of seconds on a response before failing ''' - ret = yield self.message_client.send(self._package_load(load), timeout=timeout) + ret = yield self.message_client.send( + self._package_load(load), + timeout=timeout, + tries=tries, + ) + raise tornado.gen.Return(ret) @tornado.gen.coroutine @@ -319,6 +330,8 @@ class AsyncZeroMQPubChannel(salt.transport.mixins.auth.AESPubClientMixin, salt.t # TODO: Optionally call stream.close() on newer pyzmq? Its broken on some self._stream.io_loop.remove_handler(self._stream.socket) self._stream.socket.close(0) + elif hasattr(self, '_socket'): + self._socket.close(0) if hasattr(self, 'context'): self.context.term() @@ -827,15 +840,31 @@ class AsyncReqMessageClient(object): :raises: SaltReqTimeoutError ''' + future = self.send_future_map.pop(message) del self.send_timeout_map[message] - self.send_future_map.pop(message).set_exception(SaltReqTimeoutError('Message timed out')) + if future.attempts < future.tries: + future.attempts += 1 + log.debug('SaltReqTimeoutError, retrying. ({0}/{1})'.format(future.attempts, future.tries)) + self.send( + message, + timeout=future.timeout, + tries=future.tries, + future=future, + ) - def send(self, message, timeout=None, callback=None): + else: + future.set_exception(SaltReqTimeoutError('Message timed out')) + + def send(self, message, timeout=None, tries=3, future=None, callback=None): ''' Return a future which will be completed when the message has a response ''' message = self.serial.dumps(message) - future = tornado.concurrent.Future() + if future is None: + future = tornado.concurrent.Future() + future.tries = tries + future.attempts = 0 + future.timeout = timeout if callback is not None: def handle_future(future): response = future.result() diff --git a/salt/utils/__init__.py b/salt/utils/__init__.py index 080b0178c5..ce86299da4 100644 --- a/salt/utils/__init__.py +++ b/salt/utils/__init__.py @@ -2006,7 +2006,7 @@ def get_hash(path, form='md5', chunk_size=65536): def namespaced_function(function, global_dict, defaults=None): ''' - Redefine(clone) a function under a different globals() namespace scope + Redefine (clone) a function under a different globals() namespace scope ''' if defaults is None: defaults = function.__defaults__ @@ -2021,6 +2021,32 @@ def namespaced_function(function, global_dict, defaults=None): return new_namespaced_function +def alias_function(fun, name, doc=None): + ''' + Copy a function + ''' + alias_fun = types.FunctionType(fun.__code__, + fun.__globals__, + name, + fun.__defaults__, + fun.__closure__) + alias_fun.__dict__.update(fun.__dict__) + + if doc and isinstance(doc, six.string_types): + alias_fun.__doc__ = doc + else: + if six.PY3: + orig_name = fun.__name__ + else: + orig_name = fun.func_name # pylint: disable=incompatible-py3-code + + alias_msg = ('\nThis function is an alias of ' + '``{0}``.\n'.format(orig_name)) + alias_fun.__doc__ = alias_msg + fun.__doc__ + + return alias_fun + + def _win_console_event_handler(event): if event == 5: # Do nothing on CTRL_LOGOFF_EVENT diff --git a/salt/utils/http.py b/salt/utils/http.py index c3e3c4d361..2b33010296 100644 --- a/salt/utils/http.py +++ b/salt/utils/http.py @@ -187,10 +187,11 @@ def query(url, # Make sure no secret fields show up in logs if isinstance(data, dict): log_data = data.copy() - for item in data: - for field in hide_fields: - if item == field: - log_data[item] = 'XXXXXXXXXX' + if isinstance(hide_fields, list): + for item in data: + for field in hide_fields: + if item == field: + log_data[item] = 'XXXXXXXXXX' log.trace('Request POST Data: {0}'.format(pprint.pformat(log_data))) else: log.trace('Request POST Data: {0}'.format(pprint.pformat(data))) diff --git a/salt/utils/minions.py b/salt/utils/minions.py index 3cbc37a9e3..39a394fed3 100644 --- a/salt/utils/minions.py +++ b/salt/utils/minions.py @@ -100,11 +100,15 @@ def get_minion_data(minion, opts): return minion if minion else None, None, None -def nodegroup_comp(nodegroup, nodegroups, skip=None): +def nodegroup_comp(nodegroup, nodegroups, skip=None, first_call=True): ''' Recursively expand ``nodegroup`` from ``nodegroups``; ignore nodegroups in ``skip`` - ''' + If a top-level (non-recursive) call finds no nodegroups, return the original + nodegroup definition (for backwards compatibility). Keep track of recursive + calls via `first_call` argument + ''' + expanded_nodegroup = False if skip is None: skip = set() elif nodegroup in skip: @@ -134,7 +138,8 @@ def nodegroup_comp(nodegroup, nodegroups, skip=None): if word in opers: ret.append(word) elif len(word) >= 3 and word.startswith('N@'): - ret.extend(nodegroup_comp(word[2:], nodegroups, skip=skip)) + expanded_nodegroup = True + ret.extend(nodegroup_comp(word[2:], nodegroups, skip=skip, first_call=False)) else: ret.append(word) @@ -145,7 +150,15 @@ def nodegroup_comp(nodegroup, nodegroups, skip=None): skip.remove(nodegroup) log.debug('nodegroup_comp({0}) => {1}'.format(nodegroup, ret)) - return ret + # Only return list form if a nodegroup was expanded. Otherwise return + # the original string to conserve backwards compat + if expanded_nodegroup or not first_call: + return ret + else: + log.debug('No nested nodegroups detected. ' + 'Using original nodegroup definition: {0}' + .format(nodegroups[nodegroup])) + return nodegroups[nodegroup] class CkMinions(object): diff --git a/salt/utils/openstack/nova.py b/salt/utils/openstack/nova.py index 99880e20ce..bcdd9f793c 100644 --- a/salt/utils/openstack/nova.py +++ b/salt/utils/openstack/nova.py @@ -681,17 +681,20 @@ class SaltNova(OpenStackComputeShell): nt_ks = self.compute_conn ret = {} for item in nt_ks.servers.list(): - ret[item.name] = { - 'id': item.id, - 'name': item.name, - 'state': item.status, - 'accessIPv4': item.accessIPv4, - 'accessIPv6': item.accessIPv6, - 'flavor': {'id': item.flavor['id'], - 'links': item.flavor['links']}, - 'image': {'id': item.image['id'], - 'links': item.image['links']}, - } + try: + ret[item.name] = { + 'id': item.id, + 'name': item.name, + 'state': item.status, + 'accessIPv4': item.accessIPv4, + 'accessIPv6': item.accessIPv6, + 'flavor': {'id': item.flavor['id'], + 'links': item.flavor['links']}, + 'image': {'id': item.image['id'], + 'links': item.image['links']}, + } + except TypeError: + pass return ret def server_list_detailed(self): @@ -701,28 +704,31 @@ class SaltNova(OpenStackComputeShell): nt_ks = self.compute_conn ret = {} for item in nt_ks.servers.list(): - ret[item.name] = { - 'OS-EXT-SRV-ATTR': {}, - 'OS-EXT-STS': {}, - 'accessIPv4': item.accessIPv4, - 'accessIPv6': item.accessIPv6, - 'addresses': item.addresses, - 'created': item.created, - 'flavor': {'id': item.flavor['id'], - 'links': item.flavor['links']}, - 'hostId': item.hostId, - 'id': item.id, - 'image': {'id': item.image['id'], - 'links': item.image['links']}, - 'key_name': item.key_name, - 'links': item.links, - 'metadata': item.metadata, - 'name': item.name, - 'state': item.status, - 'tenant_id': item.tenant_id, - 'updated': item.updated, - 'user_id': item.user_id, - } + try: + ret[item.name] = { + 'OS-EXT-SRV-ATTR': {}, + 'OS-EXT-STS': {}, + 'accessIPv4': item.accessIPv4, + 'accessIPv6': item.accessIPv6, + 'addresses': item.addresses, + 'created': item.created, + 'flavor': {'id': item.flavor['id'], + 'links': item.flavor['links']}, + 'hostId': item.hostId, + 'id': item.id, + 'image': {'id': item.image['id'], + 'links': item.image['links']}, + 'key_name': item.key_name, + 'links': item.links, + 'metadata': item.metadata, + 'name': item.name, + 'state': item.status, + 'tenant_id': item.tenant_id, + 'updated': item.updated, + 'user_id': item.user_id, + } + except TypeError: + continue ret[item.name]['progress'] = getattr(item, 'progress', '0') diff --git a/tests/integration/modules/pillar.py b/tests/integration/modules/pillar.py index 4318ddcb4d..e1cba4498f 100644 --- a/tests/integration/modules/pillar.py +++ b/tests/integration/modules/pillar.py @@ -113,20 +113,6 @@ class PillarModuleTest(integration.ModuleCase): self.assertEqual(grepo.rp_location, repo.remotes.origin.url) - @skipIf(HAS_GIT_PYTHON is False, - 'GitPython must be installed and >= version {0}'.format(GIT_PYTHON)) - def test_ext_pillar_env_mapping(self): - import os - from salt.pillar import git_pillar - - repo_url = 'https://github.com/saltstack/pillar1.git' - pillar = self.run_function('pillar.data') - - for branch, env in [('dev', 'testing')]: - repo = git_pillar._LegacyGitPillar(branch, repo_url, self.master_opts) - - self.assertIn(repo.working_dir, - pillar['test_ext_pillar_opts']['pillar_roots'][env]) if __name__ == '__main__': from integration import run_tests diff --git a/tests/unit/modules/blockdev_test.py b/tests/unit/modules/blockdev_test.py index 1d2b752715..e8b003b2b7 100644 --- a/tests/unit/modules/blockdev_test.py +++ b/tests/unit/modules/blockdev_test.py @@ -11,6 +11,7 @@ ensure_in_syspath('../../') # Import Salt Libs import salt.modules.blockdev as blockdev +import salt.utils blockdev.__salt__ = { 'cmd.has_exec': MagicMock(return_value=True), @@ -24,6 +25,7 @@ class TestBlockdevModule(TestCase): with patch.dict(blockdev.__salt__, {'disk.dump': MagicMock(return_value=True)}): self.assertTrue(blockdev.dump('/dev/sda')) + @skipIf(not salt.utils.which('wipefs'), 'Wipefs not found') def test_wipe(self): with patch.dict(blockdev.__salt__, {'disk.wipe': MagicMock(return_value=True)}): self.assertTrue(blockdev.wipe('/dev/sda')) @@ -35,6 +37,36 @@ class TestBlockdevModule(TestCase): ret = blockdev.tune('/dev/sda', **kwargs) self.assertTrue(ret) + def test_format(self): + ''' + unit tests for blockdev.format + ''' + device = '/dev/sdX1' + fs_type = 'ext4' + mock = MagicMock(return_value=0) + with patch.dict(blockdev.__salt__, {'cmd.retcode': mock}): + self.assertEqual(blockdev.format_(device), True) + + def test_fstype(self): + ''' + unit tests for blockdev.fstype + ''' + device = '/dev/sdX1' + fs_type = 'ext4' + mock = MagicMock(return_value='FSTYPE\n{0}'.format(fs_type)) + with patch.dict(blockdev.__salt__, {'cmd.run': mock}): + self.assertEqual(blockdev.fstype(device), fs_type) + + def test_resize2fs(self): + ''' + unit tests for blockdev.resize2fs + ''' + device = '/dev/sdX1' + mock = MagicMock() + with patch.dict(blockdev.__salt__, {'cmd.run_all': mock}): + blockdev.resize2fs(device) + mock.assert_called_once_with('resize2fs {0}'.format(device), python_shell=False) + if __name__ == '__main__': from integration import run_tests diff --git a/tests/unit/modules/boto_vpc_test.py b/tests/unit/modules/boto_vpc_test.py index 089f69b208..c679b9d97b 100644 --- a/tests/unit/modules/boto_vpc_test.py +++ b/tests/unit/modules/boto_vpc_test.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- -# TODO: Update skipped tests to expect dicttionary results from the execution +# TODO: Update skipped tests to expect dictionary results from the execution # module functions. # Import Python libs from __future__ import absolute_import - from distutils.version import LooseVersion # pylint: disable=import-error,no-name-in-module # Import Salt Testing libs @@ -132,14 +131,14 @@ class BotoVpcTestCaseMixin(object): _maybe_set_tags(tags, vpc) return vpc - def _create_subnet(self, vpc_id, cidr_block='10.0.0.0/25', name=None, tags=None): + def _create_subnet(self, vpc_id, cidr_block='10.0.0.0/25', name=None, tags=None, availability_zone=None): ''' Helper function to create a test subnet ''' if not self.conn: self.conn = boto.vpc.connect_to_region(region) - subnet = self.conn.create_subnet(vpc_id, cidr_block) + subnet = self.conn.create_subnet(vpc_id, cidr_block, availability_zone=availability_zone) _maybe_set_name_tag(name, subnet) _maybe_set_tags(tags, subnet) return subnet @@ -795,6 +794,16 @@ class BotoVpcSubnetsTestCase(BotoVpcTestCaseBase, BotoVpcTestCaseMixin): self.assertEqual(set(describe_subnet_results['subnets'][0].keys()), set(['id', 'cidr_block', 'availability_zone', 'tags'])) + @mock_ec2 + def test_create_subnet_passes_availability_zone(self): + ''' + Tests that the availability_zone kwarg is passed on to _create_resource + ''' + vpc = self._create_vpc() + self._create_subnet(vpc.id, name='subnet1', availability_zone='us-east-1a') + describe_subnet_results = boto_vpc.describe_subnets(subnet_names=['subnet1']) + self.assertEqual(describe_subnet_results['subnets'][0]['availability_zone'], 'us-east-1a') + @skipIf(NO_MOCK, NO_MOCK_REASON) @skipIf(HAS_BOTO is False, 'The boto module must be installed.') diff --git a/tests/unit/modules/event_test.py b/tests/unit/modules/event_test.py index e057651547..455672c8f4 100644 --- a/tests/unit/modules/event_test.py +++ b/tests/unit/modules/event_test.py @@ -50,7 +50,8 @@ class EventTestCase(TestCase): self.assertTrue(event.fire_master('data', 'tag')) with patch.dict(event.__opts__, {'transport': 'A', - 'id': 'id'}): + 'id': 'id', + 'master_uri': 'localhost'}): with patch.object(salt_crypt_sauth, 'gen_token', return_value='tok'): with patch.object(salt_transport_channel_factory, 'send', diff --git a/tests/unit/modules/rabbitmq_test.py b/tests/unit/modules/rabbitmq_test.py index ef753ac5b9..1832c5930a 100644 --- a/tests/unit/modules/rabbitmq_test.py +++ b/tests/unit/modules/rabbitmq_test.py @@ -21,6 +21,7 @@ ensure_in_syspath('../../') # Import Salt Libs from salt.modules import rabbitmq +from salt.exceptions import CommandExecutionError # Globals rabbitmq.__salt__ = {} @@ -114,9 +115,7 @@ class RabbitmqTestCase(TestCase): with patch.dict(rabbitmq.__salt__, {'cmd.run': mock_run}): with patch.object(rabbitmq, 'clear_password', return_value={'Error': 'Error', 'retcode': 1}): - self.assertDictEqual(rabbitmq.add_user('saltstack'), - {'Error': {'Error': 'Error', - 'retcode': 1}}) + self.assertRaises(CommandExecutionError, rabbitmq.add_user, 'saltstack') # 'delete_user' function tests: 1 @@ -352,9 +351,9 @@ class RabbitmqTestCase(TestCase): ''' Test if it return whether the plugin is enabled. ''' - mock_run = MagicMock(return_value='saltstack') + mock_run = MagicMock(return_value={'retcode': 0, 'stdout': 'saltstack'}) mock_pkg = MagicMock(return_value='') - with patch.dict(rabbitmq.__salt__, {'cmd.run': mock_run, + with patch.dict(rabbitmq.__salt__, {'cmd.run_all': mock_run, 'pkg.version': mock_pkg}): self.assertTrue(rabbitmq.plugin_is_enabled('salt')) diff --git a/tests/unit/pillar_test.py b/tests/unit/pillar_test.py index d6976664b0..e9aff93ffd 100644 --- a/tests/unit/pillar_test.py +++ b/tests/unit/pillar_test.py @@ -130,6 +130,38 @@ class PillarTestCase(TestCase): pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'base') self.assertEqual(pillar.compile_pillar()['ssh'], 'foo') + @patch('salt.pillar.salt.fileclient.get_file_client', autospec=True) + @patch('salt.pillar.salt.minion.Matcher') # autospec=True disabled due to py3 mock bug + def test_pillar_multiple_matches(self, Matcher, get_file_client): + # Uses the ``recurse_list`` strategy. + opts = { + 'renderer': 'yaml', + 'state_top': '', + 'pillar_roots': [], + 'extension_modules': '', + 'environment': 'base', + 'file_roots': [], + 'pillar_source_merging_strategy': 'recurse_list', + } + grains = { + 'os': 'Ubuntu', + 'os_family': 'Debian', + 'oscodename': 'raring', + 'osfullname': 'Ubuntu', + 'osrelease': '13.04', + 'kernel': 'Linux' + } + self._setup_test_topfile_mocks(Matcher, get_file_client, 1, 2) + pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'base') + # Pillars should be merged, but only once per pillar file. + self.assertDictEqual(pillar.compile_pillar()['generic'], { + 'key1': ['value1', 'value2', 'value3'], + 'key2': { + 'sub_key1': [], + 'sub_key2': [], + } + }) + def _setup_test_topfile_mocks(self, Matcher, get_file_client, nodegroup_order, glob_order): # Write a simple topfile and two pillar state files @@ -140,9 +172,13 @@ base: - match: nodegroup - order: {nodegroup_order} - ssh + - generic + '*': + - generic minion: - order: {glob_order} - ssh.minion + - generic.minion '''.format(nodegroup_order=nodegroup_order, glob_order=glob_order) self.top_file.write(salt.utils.to_bytes(s)) self.top_file.flush() @@ -158,6 +194,25 @@ ssh: bar ''') self.ssh_minion_file.flush() + self.generic_file = tempfile.NamedTemporaryFile() + self.generic_file.write(b''' +generic: + key1: + - value1 + - value2 + key2: + sub_key1: [] +''') + self.generic_file.flush() + self.generic_minion_file = tempfile.NamedTemporaryFile() + self.generic_minion_file.write(b''' +generic: + key1: + - value3 + key2: + sub_key2: [] +''') + self.generic_minion_file.flush() # Setup Matcher mock matcher = Matcher.return_value @@ -171,6 +226,88 @@ ssh: return { 'ssh': {'path': '', 'dest': self.ssh_file.name}, 'ssh.minion': {'path': '', 'dest': self.ssh_minion_file.name}, + 'generic': {'path': '', 'dest': self.generic_file.name}, + 'generic.minion': {'path': '', 'dest': self.generic_minion_file.name}, + }[sls] + + client.get_state.side_effect = get_state + + @patch('salt.pillar.salt.fileclient.get_file_client', autospec=True) + @patch('salt.pillar.salt.minion.Matcher') # autospec=True disabled due to py3 mock bug + def test_pillar_include(self, Matcher, get_file_client): + # Uses the ``recurse_list`` strategy. + opts = { + 'renderer': 'yaml', + 'state_top': '', + 'pillar_roots': [], + 'extension_modules': '', + 'environment': 'base', + 'file_roots': [], + 'pillar_source_merging_strategy': 'recurse_list', + } + grains = { + 'os': 'Ubuntu', + 'os_family': 'Debian', + 'oscodename': 'raring', + 'osfullname': 'Ubuntu', + 'osrelease': '13.04', + 'kernel': 'Linux' + } + self._setup_test_include_mocks(Matcher, get_file_client) + pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'base').compile_pillar() + # Both pillar modules should only be loaded once. + self.assertEqual(pillar['p1'], ['value1_3', 'value1_1', 'value1_2']) + self.assertEqual(pillar['p2'], ['value2_1', 'value2_2']) + + def _setup_test_include_mocks(self, Matcher, get_file_client): + self.top_file = top_file = tempfile.NamedTemporaryFile() + top_file.write(b''' +base: + '*': + - order: 1 + - test.sub2 + minion: + - order: 2 + - test +''') + top_file.flush() + self.init_sls = init_sls = tempfile.NamedTemporaryFile() + init_sls.write(b''' +include: + - test.sub1 + - test.sub2 +''') + init_sls.flush() + self.sub1_sls = sub1_sls = tempfile.NamedTemporaryFile() + sub1_sls.write(b''' +p1: + - value1_1 + - value1_2 +''') + sub1_sls.flush() + self.sub2_sls = sub2_sls = tempfile.NamedTemporaryFile() + sub2_sls.write(b''' +p1: + - value1_3 +p2: + - value2_1 + - value2_2 +''') + sub2_sls.flush() + + # Setup Matcher mock + matcher = Matcher.return_value + matcher.confirm_top.return_value = True + + # Setup fileclient mock + client = get_file_client.return_value + client.cache_file.return_value = self.top_file.name + + def get_state(sls, env): + return { + 'test': {'path': '', 'dest': init_sls.name}, + 'test.sub1': {'path': '', 'dest': sub1_sls.name}, + 'test.sub2': {'path': '', 'dest': sub2_sls.name}, }[sls] client.get_state.side_effect = get_state diff --git a/tests/unit/states/blockdev_test.py b/tests/unit/states/blockdev_test.py index 605b06d261..75d67e8a60 100644 --- a/tests/unit/states/blockdev_test.py +++ b/tests/unit/states/blockdev_test.py @@ -76,8 +76,12 @@ class BlockdevTestCase(TestCase): ret.update({'comment': comt}) self.assertDictEqual(blockdev.formatted(name), ret) - mock = MagicMock(return_value='ext4') - with patch.dict(blockdev.__salt__, {'cmd.run': mock}): + mock_ext4 = MagicMock(return_value='ext4') + mock_t = MagicMock(return_value=True) + mock_e = MagicMock(return_value='') + with patch.dict(blockdev.__salt__, {'cmd.run': mock_ext4, + 'blockdev.format': mock_t, + 'blockdev.fstype': mock_e}): comt = ('{0} already formatted with '.format(name)) ret.update({'comment': comt, 'result': True}) self.assertDictEqual(blockdev.formatted(name, fs_type=''), ret) diff --git a/tests/unit/states/file_test.py b/tests/unit/states/file_test.py index ab27786e6b..824c0ddf59 100644 --- a/tests/unit/states/file_test.py +++ b/tests/unit/states/file_test.py @@ -63,7 +63,7 @@ class TestFileState(TestCase): self.assertEqual(json.loads(returner.returned), dataset) filestate.serialize('/tmp', dataset, formatter="python") - self.assertEqual(returner.returned, pprint.pformat(dataset)) + self.assertEqual(returner.returned, pprint.pformat(dataset) + '\n') def test_contents_and_contents_pillar(self): def returner(contents, *args, **kwargs): @@ -420,6 +420,7 @@ class FileTestCase(TestCase): mock_t = MagicMock(return_value=True) mock_f = MagicMock(return_value=False) + mock_cmd_fail = MagicMock(return_value={'retcode': 1}) mock_uid = MagicMock(side_effect=['', 'U12', 'U12', 'U12', 'U12', 'U12', 'U12', 'U12', 'U12', 'U12', 'U12', 'U12', 'U12', 'U12', 'U12', 'U12']) @@ -451,7 +452,7 @@ class FileTestCase(TestCase): 'file.source_list': mock_file, 'file.copy': mock_cp, 'file.manage_file': mock_ex, - 'cmd.retcode': mock_t}): + 'cmd.run_all': mock_cmd_fail}): comt = ('Must provide name to file.exists') ret.update({'comment': comt, 'name': '', 'pchanges': {}}) self.assertDictEqual(filestate.managed(''), ret) @@ -1595,8 +1596,8 @@ class FileTestCase(TestCase): ret = {'comment': 'check_cmd execution failed', 'result': False, 'skip_watch': True} - mock = MagicMock(side_effect=[1, 0]) - with patch.dict(filestate.__salt__, {'cmd.retcode': mock}): + mock = MagicMock(side_effect=[{'retcode': 1}, {'retcode': 0}]) + with patch.dict(filestate.__salt__, {'cmd.run_all': mock}): self.assertDictEqual(filestate.mod_run_check_cmd(cmd, filename), ret) diff --git a/tests/unit/states/rabbitmq_plugin_test.py b/tests/unit/states/rabbitmq_plugin_test.py index fd8c5fddfa..801ddea2fc 100644 --- a/tests/unit/states/rabbitmq_plugin_test.py +++ b/tests/unit/states/rabbitmq_plugin_test.py @@ -46,13 +46,14 @@ class RabbitmqPluginTestCase(TestCase): mock = MagicMock(side_effect=[True, False]) with patch.dict(rabbitmq_plugin.__salt__, {'rabbitmq.plugin_is_enabled': mock}): - comt = ('Plugin some_plugin is already enabled') - ret.update({'comment': comt}) + comment = 'Plugin \'some_plugin\' is already enabled.' + ret.update({'comment': comment}) self.assertDictEqual(rabbitmq_plugin.enabled(name), ret) with patch.dict(rabbitmq_plugin.__opts__, {'test': True}): - comt = ('Plugin some_plugin is set to be enabled') - ret.update({'comment': comt, 'result': None}) + comment = 'Plugin \'some_plugin\' is set to be enabled.' + changes = {'new': 'some_plugin', 'old': ''} + ret.update({'comment': comment, 'result': None, 'changes': changes}) self.assertDictEqual(rabbitmq_plugin.enabled(name), ret) # 'disabled' function tests: 1 @@ -71,13 +72,14 @@ class RabbitmqPluginTestCase(TestCase): mock = MagicMock(side_effect=[False, True]) with patch.dict(rabbitmq_plugin.__salt__, {'rabbitmq.plugin_is_enabled': mock}): - comt = ('Plugin some_plugin is not enabled') - ret.update({'comment': comt}) + comment = 'Plugin \'some_plugin\' is already disabled.' + ret.update({'comment': comment}) self.assertDictEqual(rabbitmq_plugin.disabled(name), ret) with patch.dict(rabbitmq_plugin.__opts__, {'test': True}): - comt = ('Plugin some_plugin is set to be disabled') - ret.update({'comment': comt, 'result': None}) + comment = 'Plugin \'some_plugin\' is set to be disabled.' + changes = {'new': '', 'old': 'some_plugin'} + ret.update({'comment': comment, 'result': None, 'changes': changes}) self.assertDictEqual(rabbitmq_plugin.disabled(name), ret) diff --git a/tests/unit/states/rabbitmq_user_test.py b/tests/unit/states/rabbitmq_user_test.py index 6d3c417896..96f2a04fb5 100644 --- a/tests/unit/states/rabbitmq_user_test.py +++ b/tests/unit/states/rabbitmq_user_test.py @@ -21,7 +21,7 @@ ensure_in_syspath('../../') # Import Salt Libs from salt.states import rabbitmq_user -rabbitmq_user.__opts__ = {} +rabbitmq_user.__opts__ = {'test': False} rabbitmq_user.__salt__ = {} @@ -56,38 +56,40 @@ class RabbitmqUserTestCase(TestCase): '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}) + comment = 'User \'foo\' is already present.' + ret.update({'comment': comment}) 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}) + comment = 'User \'foo\' is set to be created.' + changes = {'user': {'new': 'foo', 'old': ''}} + ret.update({'comment': comment, 'result': None, 'changes': changes}) self.assertDictEqual(rabbitmq_user.present(name), ret) - comt = ("User foo's password is set to be updated") - ret.update({'comment': comt}) + comment = 'Configuration for \'foo\' will change.' + changes = {'password': {'new': 'Set password.', 'old': ''}} + ret.update({'comment': comment, 'changes': changes}) 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}) + changes = {'password': {'new': '', 'old': 'Removed password.'}} + ret.update({'changes': changes}) self.assertDictEqual(rabbitmq_user.present(name, force=True), ret) - comt = ('Tags for user foo is set to be changed') - ret.update({'comment': comt}) + changes = {'tags': {'new': set(['e', 'r', 's', 'u']), 'old': 'user'}} + ret.update({'changes': changes}) self.assertDictEqual(rabbitmq_user.present(name, tags=tag), ret) - comt = ('Permissions for user foo is set to be changed') - ret.update({'comment': comt}) + comment = '\'foo\' is already in the desired state.' + ret.update({'changes': {}, 'comment': comment, 'result': True}) 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': ''}}) + ret.update({'comment': '\'foo\' was configured.', 'result': True, + 'changes': {'tags': {'new': set(['e', 'r', 's', 'u']), 'old': 'user'}}}) self.assertDictEqual(rabbitmq_user.present(name, tags=tag), ret) # 'absent' function tests: 1 @@ -101,7 +103,7 @@ class RabbitmqUserTestCase(TestCase): ret = {'name': name, 'changes': {}, 'result': True, - 'comment': 'User {0} is not present'.format(name)} + 'comment': 'The user \'foo\' is not present.'} mock = MagicMock(return_value=False) with patch.dict(rabbitmq_user.__salt__, {'rabbitmq.user_exists': mock}):