From b19b95d992aacbd8340f4c4d59d5ff045ca6def1 Mon Sep 17 00:00:00 2001 From: rallytime Date: Fri, 17 Apr 2015 11:41:07 -0600 Subject: [PATCH 01/47] Added release note for 2014.7.5 release --- doc/topics/releases/2014.7.5.rst | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 doc/topics/releases/2014.7.5.rst diff --git a/doc/topics/releases/2014.7.5.rst b/doc/topics/releases/2014.7.5.rst new file mode 100644 index 0000000000..791f9b63ea --- /dev/null +++ b/doc/topics/releases/2014.7.5.rst @@ -0,0 +1,59 @@ +=========================== +Salt 2014.7.5 Release Notes +=========================== + +:release: TBA + +Version 2014.7.5 is a bugfix release for :doc:`2014.7.0 +`. + +Changes: + +- +- Fixed a key error bug in salt-cloud +- Updated man pages to better match documentation +- Fixed bug concerning high CPU usage with salt-ssh +- Fixed bugs with remounting cvfs and fuse filesystems +- Fixed bug with alowing requisite tracking of entire sls files +- Fixed bug with aptpkg.mod_repo returning OK even if apt-add-repository fails +- Increased frequency of ssh terminal output checking +- Fixed malformed locale string in localmod module +- Fixed checking of available version of package when accept_keywords were changed +- Fixed bug to make git.latest work with empty repositories +- Added **kwargs to service.mod_watch which removes warnings about `enable` and `__reqs__` not being supported by the function +- Improved state comments to not grow so quickly on failed requisites +- Added force argument to service to trigger force_reload +- Fixed bug to andle pkgrepo keyids that have been converted to int +- Fixed module.portage_config bug with appending accept_keywords +- Fixed bug to correctly report disk usage on windows minion +- Added the ability to specify key prefix for S3 ext_pillar +- Fixed issues with batch mode operating on the incorrect number of minions +- Fixed a bug with the proxmox cloud provider stacktracing on disk definition +- Fixed a bug with the changes dictionary in the file state +- Fixed the TCP keep alive settings to work better with SREQ caching +- Fixed many bugs within the iptables state and module +- Fixed bug with states by adding `fun`, `state`, and `unless` to the state runtime internal keywords listing +- Added ability to eAuth against Active Directory +- Fixed some salt-ssh issues when running on Fedora 21 +- Fixed grains.get_or_set_hash to work with multiple entries under same key +- Added better explanations and more examples of how the Reactor calls functions to docs +- Fixed bug to not pass `ex_config_drive` to libcloud unless it's explicitly enabled +- Fixed bug with pip.install on windows +- Fixed bug where puppet.run always returns a 0 retcode +- Fixed race condition bug with minion scheduling via pillar +- Made efficiency improvements and bug fixes to the windows installer +- Updated environment variables to fix bug with pygit2 when running salt as non-root user +- Fixed cas behavior on data module -- data.cas was not saving changes +- Fixed GPG rendering error +- Fixed strace error in virt.query +- Fixed stacktrace when running chef-solo command +- Fixed possible bug wherein uncaught exceptions seem to make zmq3 tip over when threading is involved +- Fixed argument passing to the reactor +- Fixed glibc caching to prevent bug where salt-minion getaddrinfo in dns_check() never got updated nameservers + +Known issues: + +- In multimaster mode, a minion may become temporarily unresponsive + if modules or pillars are refreshed at the same time that one + or more masters are down. This can be worked around by setting + 'auth_timeout' and 'auth_tries' down to shorter periods. From fde1feed466d32ff2e95aef9334a27e199d07e65 Mon Sep 17 00:00:00 2001 From: rallytime Date: Fri, 17 Apr 2015 11:42:14 -0600 Subject: [PATCH 02/47] Remove extra line --- doc/topics/releases/2014.7.5.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/topics/releases/2014.7.5.rst b/doc/topics/releases/2014.7.5.rst index 791f9b63ea..47ade5b4c9 100644 --- a/doc/topics/releases/2014.7.5.rst +++ b/doc/topics/releases/2014.7.5.rst @@ -9,7 +9,6 @@ Version 2014.7.5 is a bugfix release for :doc:`2014.7.0 Changes: -- - Fixed a key error bug in salt-cloud - Updated man pages to better match documentation - Fixed bug concerning high CPU usage with salt-ssh From 9d394dfe46ee09a9b1605a18dab5cac1a746ee76 Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Fri, 17 Apr 2015 12:49:54 -0500 Subject: [PATCH 03/47] Improve error logging for pygit2 SSH-based remotes If libgit2 is compiled without libssh2, an "Unsupported URL protocol" GitError exception is raised. This commit catches this exception and, if the credentials are a keypair (which means SSH auth), logs an meaningful error message that will let the user know that libgit2 was probably not compiled with libssh2. --- salt/fileserver/gitfs.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/salt/fileserver/gitfs.py b/salt/fileserver/gitfs.py index 4b208a7175..1d7e2013d5 100644 --- a/salt/fileserver/gitfs.py +++ b/salt/fileserver/gitfs.py @@ -1147,7 +1147,21 @@ def update(): except KeyError: # No credentials configured for this repo pass - fetch = origin.fetch() + try: + fetch = origin.fetch() + except pygit2.errors.GitError as exc: + # Using exc.__str__() here to avoid deprecation warning + # when referencing exc.message + if 'unsupported url protocol' in exc.__str__().lower() \ + and isinstance(repo.get('credentials'), + pygit2.Keypair): + log.error( + 'Unable to fetch SSH-based gitfs remote {0}. ' + 'libgit2 must be compiled with libssh2 to support ' + 'SSH authentication.'.format(repo['url']) + ) + continue + raise try: # pygit2.Remote.fetch() returns a dict in pygit2 < 0.21.0 received_objects = fetch['received_objects'] From 2093bf8d963e9643baa7453f0a58d2ea4be0c247 Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Fri, 17 Apr 2015 13:02:50 -0500 Subject: [PATCH 04/47] Adjust loglevels for gitfs errors The "critical" loglevel should be used for hard failures only. --- salt/fileserver/gitfs.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/salt/fileserver/gitfs.py b/salt/fileserver/gitfs.py index 1d7e2013d5..0d14487077 100644 --- a/salt/fileserver/gitfs.py +++ b/salt/fileserver/gitfs.py @@ -572,7 +572,7 @@ def _verify_auth(repo): ''' Helper function to log errors about missing auth parameters ''' - log.error( + log.critical( 'Incomplete authentication information for remote {0}. Missing ' 'parameters: {1}'.format(remote_url, ', '.join(missing)) ) @@ -595,7 +595,7 @@ def _verify_auth(repo): user = address.split('@')[0] if user == address: # No '@' sign == no user. This is a problem. - log.error( + log.critical( 'Password / keypair specified for remote {0}, but remote ' 'URL is missing a username'.format(repo['url']) ) @@ -620,7 +620,7 @@ def _verify_auth(repo): return True if password_ok: if transport == 'http' and not repo['insecure_auth']: - log.error( + log.critical( 'Invalid configuration for gitfs remote {0}. ' 'Authentication is disabled by default on http remotes. ' 'Either set gitfs_insecure_auth to True in the master ' @@ -636,7 +636,7 @@ def _verify_auth(repo): missing_auth = [x for x in required_params if not bool(repo[x])] _incomplete_auth(repo['url'], missing_auth) else: - log.error( + log.critical( 'Invalid configuration for remote {0}. Unsupported transport ' '{1!r}.'.format(repo['url'], transport) ) @@ -700,7 +700,7 @@ def init(): salt.utils.repack_dictlist(remote[repo_url]).items()] ) if not per_remote_conf: - log.error( + log.critical( 'Invalid per-remote configuration for gitfs remote {0}. ' 'If no per-remote parameters are being specified, there ' 'may be a trailing colon after the URL, which should be ' @@ -811,7 +811,7 @@ def init(): '{0}'.format(exc)) if provider == 'gitpython': msg += ' Perhaps git is not available.' - log.error(msg, exc_info_on_loglevel=logging.DEBUG) + log.critical(msg, exc_info_on_loglevel=logging.DEBUG) _failhard() if new_remote: @@ -1189,14 +1189,14 @@ def update(): try: refs_post = client.fetch(path, repo['repo']) except dulwich.errors.NotGitRepository: - log.critical( + log.error( 'Dulwich does not recognize remote {0} as a valid ' 'remote URL. Perhaps it is missing \'.git\' at the ' 'end.'.format(repo['url']) ) continue except KeyError: - log.critical( + log.error( 'Local repository cachedir {0!r} (corresponding ' 'remote: {1}) has been corrupted. Salt will now ' 'attempt to remove the local checkout to allow it to ' @@ -1207,7 +1207,7 @@ def update(): try: salt.utils.rm_rf(repo['cachedir']) except OSError as exc: - log.critical( + log.error( 'Unable to remove {0!r}: {1}' .format(repo['cachedir'], exc) ) @@ -1635,7 +1635,7 @@ def _file_lists(load, form): try: os.makedirs(list_cachedir) except os.error: - log.critical('Unable to make cachedir {0}'.format(list_cachedir)) + log.error('Unable to make cachedir {0}'.format(list_cachedir)) return [] list_cache = os.path.join( list_cachedir, From 09468d26076bf0a7015201aded683c1593b39733 Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Fri, 17 Apr 2015 12:55:10 -0500 Subject: [PATCH 05/47] Fix incorrect log message In pygit2, the ssh transport doesn't accept password auth. --- salt/fileserver/gitfs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/salt/fileserver/gitfs.py b/salt/fileserver/gitfs.py index 0d14487077..937f766196 100644 --- a/salt/fileserver/gitfs.py +++ b/salt/fileserver/gitfs.py @@ -596,8 +596,8 @@ def _verify_auth(repo): if user == address: # No '@' sign == no user. This is a problem. log.critical( - 'Password / keypair specified for remote {0}, but remote ' - 'URL is missing a username'.format(repo['url']) + 'Keypair specified for remote {0}, but remote URL is missing ' + 'a username'.format(repo['url']) ) _failhard() From 98885f71d629bb662c6805df8dc94ee43345166e Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Fri, 17 Apr 2015 13:24:14 -0500 Subject: [PATCH 06/47] Add information about libssh2 requirement for pygit2 ssh auth --- doc/topics/tutorials/gitfs.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/topics/tutorials/gitfs.rst b/doc/topics/tutorials/gitfs.rst index 106cd6f833..5c8ec4c42b 100644 --- a/doc/topics/tutorials/gitfs.rst +++ b/doc/topics/tutorials/gitfs.rst @@ -59,9 +59,12 @@ be used to install it: If pygit2_ is not packaged for the platform on which the Master is running, the pygit2_ website has installation instructions here__. Keep in mind however that following these instructions will install libgit2 and pygit2_ without system -packages. +packages. Additionally, keep in mind that :ref:`SSH authentication in pygit2 +` requires libssh2_ (*not* libssh) development +libraries to be present before libgit2 is built. .. __: http://www.pygit2.org/install.html +.. _libssh2: http://www.libssh2.org/ GitPython --------- @@ -186,7 +189,7 @@ master: ``git+ssh://``. 3. Restart the master to load the new configuration. - + .. note:: @@ -539,6 +542,8 @@ an ``insecure_auth`` parameter: - password: mypassword - insecure_auth: True +.. _pygit2-authentication-ssh: + SSH ~~~ @@ -647,7 +652,7 @@ server via SSH: .. code-block:: bash $ su - Password: + Password: # ssh github.com The authenticity of host 'github.com (192.30.252.128)' can't be established. RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48. From ce88b6ad414ae13cc221cecb1e52f7b3b626b83b Mon Sep 17 00:00:00 2001 From: rallytime Date: Fri, 17 Apr 2015 13:33:22 -0600 Subject: [PATCH 07/47] Allow map file to work with softlayer Refs #17144 --- salt/cloud/clouds/softlayer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/salt/cloud/clouds/softlayer.py b/salt/cloud/clouds/softlayer.py index b88b8d3e72..68788f5455 100644 --- a/salt/cloud/clouds/softlayer.py +++ b/salt/cloud/clouds/softlayer.py @@ -571,6 +571,8 @@ def list_nodes(call=None): ret[node]['public_ips'] = nodes[node]['primaryIpAddress'] if 'primaryBackendIpAddress' in nodes[node]: ret[node]['private_ips'] = nodes[node]['primaryBackendIpAddress'] + if 'status' in nodes[node]: + ret[node]['state'] = str(nodes[node]['status']['name']) return ret From eadaead755ccdfd479f291c5930bc636261930c4 Mon Sep 17 00:00:00 2001 From: rallytime Date: Fri, 17 Apr 2015 14:13:40 -0600 Subject: [PATCH 08/47] Add 2014.7.5 links to windows installation docs --- doc/topics/installation/windows.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/topics/installation/windows.rst b/doc/topics/installation/windows.rst index 19a66afa44..e548dd435c 100644 --- a/doc/topics/installation/windows.rst +++ b/doc/topics/installation/windows.rst @@ -20,6 +20,10 @@ minion exe>` should match the contents of the corresponding md5 file. .. admonition:: Download here + * 2014.7.5 + * `Salt-Minion-2014.7.4-x86-Setup.exe `__ | `md5 `__ + * `Salt-Minion-2014.7.4-AMD64-Setup.exe `__ | `md5 `__ + * 2014.7.4 * `Salt-Minion-2014.7.4-x86-Setup.exe `__ | `md5 `__ * `Salt-Minion-2014.7.4-AMD64-Setup.exe `__ | `md5 `__ From 5931a582d19dd2344ab8d24dc918c3a5d978b974 Mon Sep 17 00:00:00 2001 From: rallytime Date: Fri, 17 Apr 2015 14:15:33 -0600 Subject: [PATCH 09/47] Replace all 4s with 5s --- doc/topics/installation/windows.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/topics/installation/windows.rst b/doc/topics/installation/windows.rst index e548dd435c..0401676cc5 100644 --- a/doc/topics/installation/windows.rst +++ b/doc/topics/installation/windows.rst @@ -21,8 +21,8 @@ minion exe>` should match the contents of the corresponding md5 file. .. admonition:: Download here * 2014.7.5 - * `Salt-Minion-2014.7.4-x86-Setup.exe `__ | `md5 `__ - * `Salt-Minion-2014.7.4-AMD64-Setup.exe `__ | `md5 `__ + * `Salt-Minion-2014.7.5-x86-Setup.exe `__ | `md5 `__ + * `Salt-Minion-2014.7.5-AMD64-Setup.exe `__ | `md5 `__ * 2014.7.4 * `Salt-Minion-2014.7.4-x86-Setup.exe `__ | `md5 `__ From d7e8741f02419d8de56b3e94116cb555320ef95f Mon Sep 17 00:00:00 2001 From: Colton Myers Date: Fri, 17 Apr 2015 14:29:23 -0600 Subject: [PATCH 10/47] Gate salt.states.file.py msgpack Fixes #22708 --- salt/states/file.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/salt/states/file.py b/salt/states/file.py index ae637a063b..2e26908cc6 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -272,7 +272,8 @@ def _load_accumulators(): with open(path, 'rb') as f: loaded = serial.load(f) return loaded if loaded else ret - except IOError: + except (IOError, NameError): + # NameError is a msgpack error from salt-ssh return ret loaded = _deserialize(_get_accumulator_filepath()) @@ -286,8 +287,12 @@ def _persist_accummulators(accumulators, accumulators_deps): 'accumulators_deps': accumulators_deps} serial = salt.payload.Serial(__opts__) - with open(_get_accumulator_filepath(), 'w+b') as f: - serial.dump(accumm_data, f) + try: + with open(_get_accumulator_filepath(), 'w+b') as f: + serial.dump(accumm_data, f) + except NameError: + # msgpack error from salt-ssh + pass def _check_user(user, group): From 02303b22ce928f276e6935b03825167fd121420b Mon Sep 17 00:00:00 2001 From: Colton Myers Date: Fri, 17 Apr 2015 14:32:02 -0600 Subject: [PATCH 11/47] Gate msgpack in salt/modules/data.py --- salt/modules/data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/salt/modules/data.py b/salt/modules/data.py index c015219d6d..b3333909c3 100644 --- a/salt/modules/data.py +++ b/salt/modules/data.py @@ -47,7 +47,7 @@ def load(): datastore_path = os.path.join(__opts__['cachedir'], 'datastore') fn_ = salt.utils.fopen(datastore_path, 'rb') return serial.load(fn_) - except (IOError, OSError): + except (IOError, OSError, NameError): return {} @@ -75,7 +75,7 @@ def dump(new_data): return True - except (IOError, OSError): + except (IOError, OSError, NameError): return False From d4da8e66a41b4770ab917bbdac540311db9d159c Mon Sep 17 00:00:00 2001 From: Colton Myers Date: Fri, 17 Apr 2015 14:33:19 -0600 Subject: [PATCH 12/47] Gate msgpack in salt/modules/saltutil.py --- salt/modules/saltutil.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/salt/modules/saltutil.py b/salt/modules/saltutil.py index 0ece8d0919..7dd3dab3a0 100644 --- a/salt/modules/saltutil.py +++ b/salt/modules/saltutil.py @@ -522,7 +522,11 @@ def find_cached_job(jid): buf = fp_.read() fp_.close() if buf: - data = serial.loads(buf) + try: + data = serial.loads(buf) + except NameError: + # msgpack error in salt-ssh + return else: return if not isinstance(data, dict): From 8901b3b5a6131e6a99930a025a929310ea410434 Mon Sep 17 00:00:00 2001 From: twangboy Date: Fri, 17 Apr 2015 14:41:46 -0600 Subject: [PATCH 13/47] Updated instructions for building salt --- doc/topics/installation/windows.rst | 364 +++++++++++++++++++++------- 1 file changed, 274 insertions(+), 90 deletions(-) diff --git a/doc/topics/installation/windows.rst b/doc/topics/installation/windows.rst index 19a66afa44..90df32ec9f 100644 --- a/doc/topics/installation/windows.rst +++ b/doc/topics/installation/windows.rst @@ -161,7 +161,7 @@ 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: -.. code-block:: bash +.. code-block:: bat Salt-Minion-0.17.0-Setup-amd64.exe /S /master=yoursaltmaster /minion-name=yourminionname @@ -169,118 +169,304 @@ installer: Setting up a Windows build environment ====================================== -1. Install the Microsoft Visual C++ 2008 SP1 Redistributable, `vcredist_x86`_ - or `vcredist_x64`_. +This document will explain how to set up a development environment for salt on +Windows. The development environment allows you to work with the source code to +customize or fix bugs. It will also allow you to build your own installation. -2. Install `msysgit`_ +The Easy Way +============ -3. Clone the Salt git repository from GitHub - - .. code-block:: bash +Prerequisite Software +--------------------- - git clone git://github.com/saltstack/salt.git +To do this the easy way you only need to install `Git for Windows`_. -4. Install the latest point release of `Python 2.7`_ for the architecture you - wish to target +Create the Build Environment +---------------------------- +1. Clone the `Salt-Windows-Dev `_ + repo from github. -5. Add C:\\Python27 and C:\\Python27\\Scripts to your system path + Open a command line and type: -6. Download and run the Setuptools bootstrap - `ez_setup.py`_ + .. code-block:: bat - .. code-block:: bash + git clone https://github.com/saltstack/salt-windows-dev - python ez_setup.py - -7. Install Pip +2. Build the Python Environment - .. code-block:: bash - - easy_install pip + Go into the salt-windows-dev directory. Right-click the file named + **dev_env.ps1** and select **Run with PowerShell** -8. Install the latest point release of `OpenSSL for Windows`_ + If you get an error, you may need to change the execution policy. - #. During setup, choose first option to install in Windows system - directory + Open a powershell window and type the following: -9. Install the latest point release of `M2Crypto`_ + .. code-block:: powershell - #. In general, be sure to download installers targeted at py2.7 for your - chosen architecture + Set-ExecutionPolicy RemoteSigned -10. Install the latest point release of `pycrypto`_ + This will download and install Python with all the dependencies needed to + develop and build salt. -11. Install the latest point release of `pywin32`_ +3. Build the Salt Environment -12. Install the latest point release of `Cython`_ + Right-click on the file named **dev_env_salt.ps1** and select **Run with + Powershell** -13. Install the latest point release of `jinja2`_ + This will clone salt into ``C:\Salt-Dev\salt`` and set it to the 2015.2 + branch. You could optionally run the command from a powershell window with a + ``-Version`` switch to pull a different version. For example: -14. Install the latest point release of `msgpack`_ + .. code-block:: powershell -15. Install psutil + dev_env_salt.ps1 -Version '2014.7' - .. code-block:: bash + To view a list of available branches and tags, open a command prompt in your + `C:\Salt-Dev\salt` directory and type: - easy_install psutil + .. code-block:: bat -16. Install pyzmq - - .. code-block:: bash - - easy_install pyzmq - -17. Install PyYAML - - .. code-block:: bash - - easy_install pyyaml - -18. Install bbfreeze - - .. code-block:: bash - - easy_install bbfreeze - -19. Install wmi - - .. code-block:: bash - - pip install wmi - -20. Install esky - - .. code-block:: bash - - pip install esky - -21. Install Salt - - .. code-block:: bash - - cd salt - python setup.py install - -22. Build a frozen binary distribution of Salt - - .. code-block:: bash - - python setup.py bdist_esky - -A zip file has been created in the ``dist/`` folder, containing a frozen copy -of Python and the dependency libraries, along with Windows executables for each -of the Salt scripts. + git branch -a + git tag -n -Building the installer -====================== +The Hard Way +============ -The Salt Windows installer is built with the open-source NSIS compiler. The -source for the installer is found in the pkg directory of the Salt repo here: -:blob:`pkg/windows/installer/Salt-Minion-Setup.nsi`. To create the installer, -extract the frozen archive from ``dist/`` into ``pkg/windows/buildenv/`` and -run NSIS. +Prerequisite Software +--------------------- -The NSIS installer can be found here: http://nsis.sourceforge.net/Main_Page +Install the following software: + +1. `Git for Windows `_ +2. `Nullsoft Installer `_ + +Download the Prerequisite zip file for your CPU architecture from the +SaltStack download site: + +* `Salt32.zip `_ +* `Salt64.zip `_ + +These files contain all sofware required to build and develop salt. Unzip the +contents of the file to ``C:\Salt-Dev\temp``. + +Create the Build Environment +---------------------------- + +1. Build the Python Environment + + * Install Python: + + Browse to the ``C:\Salt-Dev\temp`` directory and find the Python + installation file for your CPU Architecture under the corresponding + subfolder. Double-click the file to install python. + + Make sure the following are in your **PATH** environment variable: + + .. code-block:: bat + + C:\Python27 + C:\Python27\Scripts + + * Install Pip + + Open a command prompt and navigate to ``C:\Salt-Dev\temp`` + Run the following command: + + .. code-block:: bat + + python get-pip.py + + * Easy Install compiled binaries. + + M2Crypto, PyCrypto, and PyWin32 need to be installed using Easy Install. + Open a command prompt and navigate to ``C:\Salt-Dev\temp\``. + Run the following commands: + + .. code-block:: bat + + easy_install -Z + easy_install -Z + easy_install -Z + + .. note:: + You can type the first part of the file name and then press the tab key + to auto-complete the name of the file. + + * Pip Install Additional Prerequisites + + All remaining prerequisites need to be pip installed. These prerequisites + are as follow: + + * MarkupSafe + * Jinja + * MsgPack + * PSUtil + * PyYAML + * PyZMQ + * WMI + * Requests + * Certifi + + Open a command prompt and navigate to ``C:\Salt-Dev\temp``. Run the following + commands: + + .. code-block:: bat + + pip install \ + pip install + pip install \ + pip install \ + pip install \ + pip install \ + pip install + pip install + pip install + +2. Build the Salt Environment + + * Clone Salt + + Open a command prompt and navigate to ``C:\Salt-Dev``. Run the following command + to clone salt: + + .. code-block:: bat + + git clone https://github.com/saltstack/salt + + * Checkout Branch + + Checkout the branch or tag of salt you want to work on or build. Open a + command prompt and navigate to ``C:\Salt-Dev\salt``. Get a list of + available tags and branches by running the following commands: + + .. code-block:: bat + + git fetch --all + + To view a list of available branches: + git branch -a + + To view a list of availabel tags: + git tag -n + + Checkout the branch or tag by typing the following command: + + .. code-block:: bat + + git checkout + + * Clean the Environment + + When switching between branches residual files can be left behind that + will interfere with the functionality of salt. Therefore, after you check + out the branch you want to work on, type the following commands to clean + the salt environment: + + .. code-block: bat + + git clean -fxd + git reset --hard HEAD + + +Developing with Salt +==================== +There are two ways to develop with salt. You can run salt's setup.py each time +you make a change to source code or you can use the setup tools develop mode. + + +Configure the Minion +-------------------- +Both methods require that the minion configuration be in the ``C:\salt`` +directory. Copy the conf and var directories from ``C:\Salt-Dev\salt\pkg\ +windows\buildenv`` to ``C:\salt``. Now go into the ``C:\salt\conf`` directory +and edit the file name ``minion`` (no extension). You need to configure the +master and id parameters in this file. Edit the following lines: + +.. code-block:: bat + + master: + id: + +Setup.py Method +--------------- +Go into the ``C:\Salt-Dev\salt`` directory from a cmd prompt and type: + +.. code-block:: bat + + python setup.py install --force + +This will install python into your python installation at ``C:\Python27``. +Everytime you make an edit to your source code, you'll have to stop the minion, +run the setup, and start the minion. + +To start the salt-minion go into ``C:\Python27\Scripts`` from a cmd prompt and +type: + +.. code-block:: bat + + salt-minion + +For debug mode type: + +.. code-block:: bat + + salt-minion -l debug + +To stop the minion press Ctrl+C. + + +Setup Tools Develop Mode (Preferred Method) +------------------------------------------- +To use the Setup Tools Develop Mode go into ``C:\Salt-Dev\salt`` from a cmd +prompt and type: + +.. code-block:: bat + + pip install -e . + +This will install pointers to your source code that resides at +``C:\Salt-Dev\salt``. When you edit your source code you only have to restart +the minion. + + +Build the windows installer +=========================== +This is the method of building the installer as of version 2014.7.4. + +Clean the Environment +--------------------- +Make sure you don't have any leftover salt files from previous versions of salt +in your Python directory. + +1. Remove all files that start with salt in the ``C:\Python27\Scripts`` + directory + +2. Remove all files and directorys that start with salt in the + ``C:\Python27\Lib\site-packages`` directory + +Install Salt +------------ +Install salt using salt's setup.py. From the ``C:\Salt-Dev\salt`` directory +type the following command: + +.. code-block:: bat + + python setup.py install --force + +Build the Installer +------------------- + +From cmd prompt go into the ``C:\Salt-Dev\salt\pkg\windows`` directory. Type +the following command for the branch or tag of salt you're building: + +.. code-block:: bat + + BuildSalt.bat + +This will copy python with salt installed to the ``buildenv\bin`` directory, +make it portable, and then create the windows installer . The .exe for the +windows installer will be placed in the ``installer`` directory. Testing the Salt minion @@ -335,7 +521,7 @@ salt, including all dependencies: This script is not up to date. Please use the installer found above -.. code-block:: bash +.. code-block:: powershell # (All in one line.) @@ -359,8 +545,6 @@ this, salt-minion can't report some installed softwares. .. _http://csa-net.dk/salt: http://csa-net.dk/salt -.. _vcredist_x86: http://www.microsoft.com/en-us/download/details.aspx?id=5582 -.. _vcredist_x64: http://www.microsoft.com/en-us/download/details.aspx?id=2092 .. _msysgit: http://code.google.com/p/msysgit/downloads/list?can=3 .. _Python 2.7: http://www.python.org/downloads .. _ez_setup.py: https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py From adc421acdd1921003a98116e13a753fcb24bcc42 Mon Sep 17 00:00:00 2001 From: twangboy Date: Fri, 17 Apr 2015 15:03:47 -0600 Subject: [PATCH 14/47] Fixed some formatting issues --- doc/topics/installation/windows.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/topics/installation/windows.rst b/doc/topics/installation/windows.rst index 90df32ec9f..e4990b17f6 100644 --- a/doc/topics/installation/windows.rst +++ b/doc/topics/installation/windows.rst @@ -174,15 +174,16 @@ Windows. The development environment allows you to work with the source code to customize or fix bugs. It will also allow you to build your own installation. The Easy Way -============ +------------ Prerequisite Software ---------------------- +^^^^^^^^^^^^^^^^^^^^^ -To do this the easy way you only need to install `Git for Windows`_. +To do this the easy way you only need to install `Git for Windows `_. Create the Build Environment ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 1. Clone the `Salt-Windows-Dev `_ repo from github. @@ -231,10 +232,10 @@ Create the Build Environment The Hard Way -============ +------------ Prerequisite Software ---------------------- +^^^^^^^^^^^^^^^^^^^^^ Install the following software: @@ -251,7 +252,7 @@ These files contain all sofware required to build and develop salt. Unzip the contents of the file to ``C:\Salt-Dev\temp``. Create the Build Environment ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Build the Python Environment From f75b24ad68843626945598adb8c5378877edea95 Mon Sep 17 00:00:00 2001 From: Viet Hung Nguyen Date: Mon, 13 Apr 2015 18:59:26 +0700 Subject: [PATCH 15/47] gracefully handle when salt-minion cannot decrypt key or salt-minion will die. Returns None to deligate this job to compile_pillar function handle it, as it is the only caller of this function for now --- salt/transport/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/salt/transport/__init__.py b/salt/transport/__init__.py index b3d4e9ffdb..b9aa87d4ab 100644 --- a/salt/transport/__init__.py +++ b/salt/transport/__init__.py @@ -242,9 +242,13 @@ class ZeroMQChannel(Channel): def crypted_transfer_decode_dictentry(self, load, dictkey=None, tries=3, timeout=60): ret = self.sreq.send('aes', self.auth.crypticle.dumps(load), tries, timeout) key = self.auth.get_keys() - aes = key.private_decrypt(ret['key'], 4) - pcrypt = salt.crypt.Crypticle(self.opts, aes) - return pcrypt.loads(ret[dictkey]) + try: + aes = key.private_decrypt(ret['key'], 4) + except (TypeError, KeyError): + return None + else: + pcrypt = salt.crypt.Crypticle(self.opts, aes) + return pcrypt.loads(ret[dictkey]) def _crypted_transfer(self, load, tries=3, timeout=60): ''' From 8f1c0084cdc4dd08f00a8a9cd391fee92e1a35e7 Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Fri, 17 Apr 2015 22:11:40 -0500 Subject: [PATCH 16/47] Clarify that for pygit2, receiving 0 objects means repo is up-to-date This seems to be too confusing to users, so this changes the log message to say that the repo is up-to-date instead of saying that 0 objects were received. --- salt/fileserver/gitfs.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/salt/fileserver/gitfs.py b/salt/fileserver/gitfs.py index 937f766196..2f0424596e 100644 --- a/salt/fileserver/gitfs.py +++ b/salt/fileserver/gitfs.py @@ -1169,10 +1169,16 @@ def update(): # pygit2.Remote.fetch() returns a class instance in # pygit2 >= 0.21.0 received_objects = fetch.received_objects - log.debug( - 'gitfs received {0} objects for remote {1}' - .format(received_objects, repo['url']) - ) + if received_objects != 0: + log.debug( + 'gitfs received {0} objects for remote {1}' + .format(received_objects, repo['url']) + ) + else: + log.debug( + 'gitfs remote {0} is up-to-date' + .format(repo['url']) + ) # Clean up any stale refs refs_post = repo['repo'].listall_references() cleaned = _clean_stale(repo['repo'], refs_post) From 619dd5cce8446e19e88a152146ce7e1047c725ae Mon Sep 17 00:00:00 2001 From: Jayesh Kariya Date: Mon, 20 Apr 2015 15:56:31 +0530 Subject: [PATCH 17/47] adding states/apache unit test case --- tests/unit/states/apache_test.py | 72 ++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/unit/states/apache_test.py diff --git a/tests/unit/states/apache_test.py b/tests/unit/states/apache_test.py new file mode 100644 index 0000000000..8f96e2706c --- /dev/null +++ b/tests/unit/states/apache_test.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch, + mock_open) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import apache +import salt.utils + +apache.__opts__ = {} +apache.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class ApacheTestCase(TestCase): + ''' + Test cases for salt.states.apache + ''' + # 'configfile' function tests: 1 + + @patch('os.path.exists', MagicMock(return_value=True)) + def test_configfile(self): + ''' + Test to allows for inputting a yaml dictionary into a file + for apache configuration files. + ''' + name = 'yaml' + config = 'VirtualHost: this: "*:80"' + + ret = {'name': name, + 'result': True, + 'changes': {}, + 'comment': ''} + + mock = MagicMock(side_effect=[config, '', '']) + with patch.object(salt.utils, 'fopen', mock_open(read_data=config)): + with patch.dict(apache.__salt__, + {'apache.config': mock}): + ret.update({'comment': 'Configuration is up to date.'}) + self.assertDictEqual(apache.configfile(name, config), ret) + + ret.update({'comment': 'Configuration will update.', + 'changes': {'new': '', + 'old': 'VirtualHost: this: "*:80"'}, + 'result': None}) + with patch.dict(apache.__opts__, {'test': True}): + self.assertDictEqual(apache.configfile(name, config), ret) + + ret.update({'comment': 'Successfully created configuration.', + 'result': True}) + with patch.dict(apache.__opts__, {'test': False}): + self.assertDictEqual(apache.configfile(name, config), ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(ApacheTestCase, needs_daemon=False) From e06343bc186add940b19cabee3f42016200bed50 Mon Sep 17 00:00:00 2001 From: Jayesh Kariya Date: Mon, 20 Apr 2015 15:57:54 +0530 Subject: [PATCH 18/47] adding states/apt unit test case --- tests/unit/states/apt_test.py | 52 +++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/unit/states/apt_test.py diff --git a/tests/unit/states/apt_test.py b/tests/unit/states/apt_test.py new file mode 100644 index 0000000000..6bceda0a5d --- /dev/null +++ b/tests/unit/states/apt_test.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import apt + +apt.__opts__ = {} +apt.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class AptTestCase(TestCase): + ''' + Test cases for salt.states.apt + ''' + # 'held' function tests: 1 + + def test_held(self): + ''' + Test to set package in 'hold' state, meaning it will not be upgraded. + ''' + name = 'tmux' + + ret = {'name': name, + 'result': False, + 'changes': {}, + 'comment': 'Package {0} does not have a state'.format(name)} + + mock = MagicMock(return_value=False) + with patch.dict(apt.__salt__, {'pkg.get_selections': mock}): + self.assertDictEqual(apt.held(name), ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(AptTestCase, needs_daemon=False) From be7e2e9202a5492cec11e30b112de54c5202c2f9 Mon Sep 17 00:00:00 2001 From: Jayesh Kariya Date: Mon, 20 Apr 2015 15:59:27 +0530 Subject: [PATCH 19/47] adding states/at unit test case --- tests/unit/states/at_test.py | 109 +++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 tests/unit/states/at_test.py diff --git a/tests/unit/states/at_test.py b/tests/unit/states/at_test.py new file mode 100644 index 0000000000..0d7206d81e --- /dev/null +++ b/tests/unit/states/at_test.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import at + +at.__salt__ = {} +at.__opts__ = {} +at.__grains__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class AtTestCase(TestCase): + ''' + Test cases for salt.states.at + ''' + # 'present' function tests: 1 + + def test_present(self): + ''' + Test to add a job to queue. + ''' + name = 'jboss' + timespec = '9:09 11/04/15' + tag = 'love' + user = 'jam' + + ret = {'name': name, + 'result': True, + 'changes': {}, + 'comment': ''} + + mock = MagicMock(return_value=False) + with patch.dict(at.__opts__, {'test': False}): + with patch.dict(at.__grains__, {'os_family': 'Redhat'}): + with patch.dict(at.__salt__, {'cmd.run': mock}): + ret.update({'comment': False}) + self.assertDictEqual(at.present(name, timespec, tag), + ret) + + with patch.dict(at.__salt__, {'user.info': mock}): + comt = 'User: {0} is not exists'.format(user) + ret.update({'comment': comt, 'result': False}) + self.assertDictEqual(at.present(name, timespec, tag, user), + ret) + + with patch.dict(at.__opts__, {'test': True}): + comt = 'job {0} is add and will run on {1}'.format(name, timespec) + ret.update({'comment': comt, 'result': None}) + self.assertDictEqual(at.present(name, timespec, tag, user), ret) + + # 'absent' function tests: 1 + + def test_absent(self): + ''' + Test to remove a job from queue + ''' + name = 'jboss' + + ret = {'name': name, + 'result': None, + 'changes': {}, + 'comment': ''} + + with patch.dict(at.__opts__, {'test': True}): + ret.update({'comment': 'Remove jobs()'}) + self.assertDictEqual(at.absent(name), ret) + + with patch.dict(at.__opts__, {'test': False}): + comt = 'limit parameter not supported {0}'.format(name) + ret.update({'comment': comt, 'result': False}) + self.assertDictEqual(at.absent(name), ret) + + mock = MagicMock(return_value={'jobs': []}) + mock_bool = MagicMock(return_value=False) + with patch.dict(at.__salt__, {'at.atq': mock, + 'cmd.run': mock_bool}): + comt = 'No match jobs or time format error' + ret.update({'comment': comt, 'result': False, 'name': 'all'}) + self.assertDictEqual(at.absent('all'), ret) + + mock = MagicMock(return_value={'jobs': [{'job': 'rose'}]}) + mock_bool = MagicMock(return_value=False) + with patch.dict(at.__salt__, {'at.atq': mock, + 'cmd.run': mock_bool}): + comt = "Remove job(rose) from queue but (['rose']) fail" + ret.update({'comment': comt, 'result': True}) + self.assertDictEqual(at.absent('all'), ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(AtTestCase, needs_daemon=False) From 58ffc0c756a5f8cf7e3846590b1334f4ca79e3d2 Mon Sep 17 00:00:00 2001 From: Jayesh Kariya Date: Mon, 20 Apr 2015 16:01:10 +0530 Subject: [PATCH 20/47] adding states/augeas unit test case --- tests/unit/states/augeas_test.py | 78 ++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/unit/states/augeas_test.py diff --git a/tests/unit/states/augeas_test.py b/tests/unit/states/augeas_test.py new file mode 100644 index 0000000000..54a9fe887e --- /dev/null +++ b/tests/unit/states/augeas_test.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch +) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import augeas + +augeas.__opts__ = {} +augeas.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class AugeasTestCase(TestCase): + ''' + Test cases for salt.states.augeas + ''' + # 'change' function tests: 1 + + def test_change(self): + ''' + Test to issue changes to Augeas, optionally for a specific context, + with a specific lens. + ''' + name = 'zabbix' + context = '/files/etc/services' + changes = ['ins service-name after service-name[last()]', + 'set service-name[last()] zabbix-agent'] + + ret = {'name': name, + 'result': False, + 'changes': {}, + 'comment': ''} + + comt = ('\'changes\' must be specified as a list') + ret.update({'comment': comt}) + self.assertDictEqual(augeas.change(name), ret) + + comt = ('Executing commands in file "/files/etc/services":\n' + 'ins service-name after service-name[last()]' + '\nset service-name[last()] zabbix-agent') + ret.update({'comment': comt, 'result': None}) + with patch.dict(augeas.__opts__, {'test': True}): + self.assertDictEqual(augeas.change(name, context, changes), ret) + + with patch.dict(augeas.__opts__, {'test': False}): + mock = MagicMock(return_value={'retval': False, 'error': 'error'}) + with patch.dict(augeas.__salt__, {'augeas.execute': mock}): + ret.update({'comment': 'Error: error', 'result': False}) + self.assertDictEqual(augeas.change(name, changes=changes), ret) + + chang = ['ins service-name after service-name[last()]', + 'set service-name[last()] zabbix-agent'] + mock = MagicMock(return_value={'retval': True}) + with patch.dict(augeas.__salt__, {'augeas.execute': mock}): + ret.update({'comment': 'Changes have been saved', + 'result': True, 'changes': chang}) + self.assertDictEqual(augeas.change(name, changes=changes), ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(AugeasTestCase, needs_daemon=False) From 446121750d537e1e7552e203b3bcb08fe2240c07 Mon Sep 17 00:00:00 2001 From: Jayesh Kariya Date: Mon, 20 Apr 2015 16:02:46 +0530 Subject: [PATCH 21/47] adding states/artifactory unit test case --- tests/unit/states/artifactory_test.py | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/unit/states/artifactory_test.py diff --git a/tests/unit/states/artifactory_test.py b/tests/unit/states/artifactory_test.py new file mode 100644 index 0000000000..13fcb92169 --- /dev/null +++ b/tests/unit/states/artifactory_test.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import artifactory + +artifactory.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class ArtifactoryTestCase(TestCase): + ''' + Test cases for salt.states.artifactory + ''' + # 'downloaded' function tests: 1 + + def test_downloaded(self): + ''' + Test to ensures that the artifact from artifactory exists at + given location. + ''' + name = 'jboss' + arti_url = 'http://artifactory.intranet.company.com/artifactory' + artifact = {'artifactory_url': arti_url, 'artifact_id': 'module', + 'repository': 'libs-release-local', 'packaging': 'jar', + 'group_id': 'com.company.module', 'classifier': 'sources', + 'version': '1.0'} + + ret = {'name': name, + 'result': False, + 'changes': {}, + 'comment': ''} + + mck = MagicMock(return_value={'status': False, 'changes': {}, + 'comment': ''}) + with patch.dict(artifactory.__salt__, {'artifactory.get_release': mck}): + self.assertDictEqual(artifactory.downloaded(name, artifact), ret) + + with patch.object(artifactory, '__fetch_from_artifactory', + MagicMock(side_effect=Exception('error'))): + self.assertEqual(artifactory.downloaded(name, artifact)['comment'] + .message, 'error') + + +if __name__ == '__main__': + from integration import run_tests + run_tests(ArtifactoryTestCase, needs_daemon=False) From 419a4837502c797e817870e09bead51e8b1ec889 Mon Sep 17 00:00:00 2001 From: Seth House Date: Mon, 20 Apr 2015 11:11:35 -0600 Subject: [PATCH 22/47] Back-ported salt-api upstart script from develop Added in ffe1ba6 --- debian/salt-api.upstart | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 debian/salt-api.upstart diff --git a/debian/salt-api.upstart b/debian/salt-api.upstart new file mode 100644 index 0000000000..c9b2531925 --- /dev/null +++ b/debian/salt-api.upstart @@ -0,0 +1,8 @@ +description "Salt API" + +start on (net-device-up + and local-filesystems + and runlevel [2345]) +stop on runlevel [!2345] + +exec salt-api From 388773010a6f14c24b0684be349af75d639f1619 Mon Sep 17 00:00:00 2001 From: Colton Myers Date: Mon, 20 Apr 2015 14:03:24 -0600 Subject: [PATCH 23/47] Add makedirs arg to shell.send --- salt/client/ssh/shell.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/salt/client/ssh/shell.py b/salt/client/ssh/shell.py index 1cd7d05e8e..3f0a1d6446 100644 --- a/salt/client/ssh/shell.py +++ b/salt/client/ssh/shell.py @@ -291,10 +291,13 @@ class Shell(object): ret = self._run_cmd(cmd) return ret - def send(self, local, remote): + def send(self, local, remote, makedirs=False): ''' scp a file or files to a remote system ''' + if makedirs: + self.exec_cmd('mkdir -p {0}'.format(os.path.dirname(remote))) + cmd = '{0} {1}:{2}'.format(local, self.host, remote) cmd = self._cmd_str(cmd, ssh='scp') From c925057e8e1046398b9e8176bfbe3e061a307712 Mon Sep 17 00:00:00 2001 From: Colton Myers Date: Mon, 20 Apr 2015 14:27:45 -0600 Subject: [PATCH 24/47] Add template, makedirs, and gzip arguments to cp.get_file (in salt-ssh) Fixes #22751 --- salt/client/ssh/wrapper/cp.py | 77 ++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/salt/client/ssh/wrapper/cp.py b/salt/client/ssh/wrapper/cp.py index ff534dc6aa..9b550279ca 100644 --- a/salt/client/ssh/wrapper/cp.py +++ b/salt/client/ssh/wrapper/cp.py @@ -6,18 +6,38 @@ from __future__ import absolute_import # Import salt libs import salt.client.ssh +import logging + +log = logging.getLogger(__name__) -def get_file(path, dest, saltenv='base'): +def get_file(path, + dest, + saltenv='base', + makedirs=False, + template=None, + gzip=None): ''' Send a file from the master to the location in specified + + .. note:: + + gzip compression is not supported in the salt-ssh version of + cp.get_file. The argument is only accepted for interface compatibility. ''' + if gzip is not None: + log.warning('The gzip argument to cp.get_file in salt-ssh is ' + 'unsupported') + + if template is not None: + (path, dest) = _render_filenames(path, dest, saltenv, template) + src = __context__['fileclient'].cache_file(path, saltenv) single = salt.client.ssh.Single( __opts__, '', **__salt__.kwargs) - ret = single.shell.send(src, dest) + ret = single.shell.send(src, dest, makedirs) return not ret[2] @@ -74,3 +94,56 @@ def list_master_symlinks(saltenv='base', prefix=''): List all of the symlinks stored on the master ''' return __context__['fileclient'].symlink_list(saltenv, prefix) + + +def _render_filenames(path, dest, saltenv, template): + ''' + Process markup in the :param:`path` and :param:`dest` variables (NOT the + files under the paths they ultimately point to) according to the markup + format provided by :param:`template`. + ''' + if not template: + return (path, dest) + + # render the path as a template using path_template_engine as the engine + if template not in salt.utils.templates.TEMPLATE_REGISTRY: + raise CommandExecutionError( + 'Attempted to render file paths with unavailable engine ' + '{0}'.format(template) + ) + + kwargs = {} + kwargs['salt'] = __salt__ + kwargs['pillar'] = __pillar__ + kwargs['grains'] = __grains__ + kwargs['opts'] = __opts__ + kwargs['saltenv'] = saltenv + + def _render(contents): + ''' + Render :param:`contents` into a literal pathname by writing it to a + temp file, rendering that file, and returning the result. + ''' + # write out path to temp file + tmp_path_fn = salt.utils.mkstemp() + with salt.utils.fopen(tmp_path_fn, 'w+') as fp_: + fp_.write(contents) + data = salt.utils.templates.TEMPLATE_REGISTRY[template]( + tmp_path_fn, + to_str=True, + **kwargs + ) + salt.utils.safe_rm(tmp_path_fn) + if not data['result']: + # Failed to render the template + raise CommandExecutionError( + 'Failed to render file path with error: {0}'.format( + data['data'] + ) + ) + else: + return data['data'] + + path = _render(path) + dest = _render(dest) + return (path, dest) From eeb42849f82993ade8b990748af0cd692bb753a6 Mon Sep 17 00:00:00 2001 From: Nitin Madhok Date: Mon, 20 Apr 2015 17:21:49 -0400 Subject: [PATCH 25/47] Backport #22860 --- salt/utils/lazy.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/salt/utils/lazy.py b/salt/utils/lazy.py index 097843623f..33c703eded 100644 --- a/salt/utils/lazy.py +++ b/salt/utils/lazy.py @@ -95,7 +95,15 @@ class LazyDict(collections.MutableMapping): ''' Check if the name is in the dict and return it if it is ''' - if name in self: + if name not in self._dict and not self.loaded: + # load the item + if self._load(name): + log.debug('LazyLoaded {0}'.format(name)) + return self._dict[name] + else: + log.debug('Could not LazyLoad {0}'.format(name)) + raise KeyError(name) + elif name in self: return self[name] raise AttributeError(name) From c235e0376b00bdd24bcad68ba2b6fe82ee718ff4 Mon Sep 17 00:00:00 2001 From: Colton Myers Date: Mon, 20 Apr 2015 15:52:25 -0600 Subject: [PATCH 26/47] Import CommandExecutionError --- salt/client/ssh/wrapper/cp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/salt/client/ssh/wrapper/cp.py b/salt/client/ssh/wrapper/cp.py index 9b550279ca..9cb76a01b9 100644 --- a/salt/client/ssh/wrapper/cp.py +++ b/salt/client/ssh/wrapper/cp.py @@ -7,6 +7,7 @@ from __future__ import absolute_import # Import salt libs import salt.client.ssh import logging +from salt.exceptions import CommandExecutionError log = logging.getLogger(__name__) From adb21a64b1052e1e6eb9217b9db9d3e83f2e319c Mon Sep 17 00:00:00 2001 From: Thomas S Hatch Date: Mon, 20 Apr 2015 16:18:30 -0600 Subject: [PATCH 27/47] py3 compat for cassanra_cql --- salt/modules/cassandra_cql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/modules/cassandra_cql.py b/salt/modules/cassandra_cql.py index 755ec3640c..35c85b1632 100644 --- a/salt/modules/cassandra_cql.py +++ b/salt/modules/cassandra_cql.py @@ -203,7 +203,7 @@ def cql_query(query, contact_points=None, port=None, cql_user=None, cql_pass=Non if results: for result in results: values = {} - for key, value in result.iteritems(): + for key, value in six.iteritems(result): # Salt won't return dictionaries with odd types like uuid.UUID if not isinstance(value, six.text_type): value = str(value) From a4e24f5c6a34969dd0a13630279afd4823023b4a Mon Sep 17 00:00:00 2001 From: salt_build Date: Mon, 20 Apr 2015 23:56:11 +0000 Subject: [PATCH 28/47] Added new panel graphic to installer --- pkg/windows/installer/Salt-Minion-Setup.nsi | 3 ++- pkg/windows/installer/panel.bmp | Bin 0 -> 154542 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 pkg/windows/installer/panel.bmp diff --git a/pkg/windows/installer/Salt-Minion-Setup.nsi b/pkg/windows/installer/Salt-Minion-Setup.nsi index ff7989e682..aa3a9b8d85 100644 --- a/pkg/windows/installer/Salt-Minion-Setup.nsi +++ b/pkg/windows/installer/Salt-Minion-Setup.nsi @@ -17,7 +17,7 @@ ${StrLoc} ${StrStrAdv} !ifdef SaltVersion - !define PRODUCT_VERSION ${SaltVersion} + !define PRODUCT_VERSION "${SaltVersion}" !else !define PRODUCT_VERSION "Undefined Version" !endif @@ -41,6 +41,7 @@ Var MinionName_State !define MUI_ABORTWARNING !define MUI_ICON "salt.ico" !define MUI_UNICON "salt.ico" +!define MUI_WELCOMEFINISHPAGE_BITMAP "panel.bmp" ; Welcome page !insertmacro MUI_PAGE_WELCOME diff --git a/pkg/windows/installer/panel.bmp b/pkg/windows/installer/panel.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5a2720bca27d03dfb8ffd230d692465a7a82f307 GIT binary patch literal 154542 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Œ zd`PVr*df7EJ@hADKW2rdy8xOZ1TrVo7}w`+4B!7<6gA;FdK-M{Zc1cL83XAWCNzUe zT(r>mf(|~pc~2CWpB*MCs+Gd5kz|=3?N0&Joc2=%y-J3aTm)-$7$e1vj}4}3Q^q>_ z!WfM)k#~NMH?vTaOdQAZUb?TqKSjFw6XGRvntkJh0~5{VSU0UYQo4?-8v~8y)oY8$ zOWqe5QCfVk@|VH`UhzXaLF&WR=~Pvzn)1l=VjcQ>XmK&!=U71x=a~32E2Q{YhEna5g0|bFswL`Xanwikr%==U=Ok1`LsZ<*E;BF18?1a}+s61GW%4=!8 zmC4pzD-(-vFRq{ol1fwh$T>*{lTaX`Kth3p0tp2Y3M3RrD3DMfp+G``gaQc#5(*>~ zNGOm{AfZ4)frJ7H1riD*6i6tLP#~c|LV<(=2?Y`gBos&}kWe6@Kth3p0tp2Y3M3Rr zD3DMfp+G``gaQc#5(*>~NGOm{AfZ4)frJ7H1riD*6i6tLP#~c|LV<(=2?Y`gBos&} gkWe6@Kth3p0tp2Y3M3RrD3DMfp+G``i%fz44*@cGj{pDw literal 0 HcmV?d00001 From 7235bb0b6886d777dfdb62685bd88449fbf3ae8b Mon Sep 17 00:00:00 2001 From: salt_build Date: Mon, 20 Apr 2015 23:59:33 +0000 Subject: [PATCH 29/47] Removed some parenthesis --- pkg/windows/installer/Salt-Minion-Setup.nsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/windows/installer/Salt-Minion-Setup.nsi b/pkg/windows/installer/Salt-Minion-Setup.nsi index aa3a9b8d85..d1c344ef36 100644 --- a/pkg/windows/installer/Salt-Minion-Setup.nsi +++ b/pkg/windows/installer/Salt-Minion-Setup.nsi @@ -17,7 +17,7 @@ ${StrLoc} ${StrStrAdv} !ifdef SaltVersion - !define PRODUCT_VERSION "${SaltVersion}" + !define PRODUCT_VERSION ${SaltVersion} !else !define PRODUCT_VERSION "Undefined Version" !endif From 5a14389839298e43b5faac9b28b0e8b93806c543 Mon Sep 17 00:00:00 2001 From: Dmitri Muntean Date: Tue, 21 Apr 2015 15:33:49 +1000 Subject: [PATCH 30/47] Encode spaces with %20 instead of plus sign in AWS request query strings It is currently not possible to set tag values (or any other values in AWS query string) to strings that contain spaces. urlencode function from urllib replaces spaces with '+' (plus sign) to produce application/x-www-form-urlencoded format, but AWS signing process document explicitly talks about encoding spaces with %20 rather than '+' (see http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html) There is an exiting issue raised in python bug tracker to have an ability to modify this behaivour of urlencode (see https://bugs.python.org/issue13866). But until it is resolved, this looks like the best way to go. --- salt/utils/aws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/utils/aws.py b/salt/utils/aws.py index fbe753da56..617ebb1de3 100644 --- a/salt/utils/aws.py +++ b/salt/utils/aws.py @@ -155,7 +155,7 @@ def sig4(method, endpoint, params, prov_dict, aws_api_version, location, params_with_headers['Version'] = aws_api_version keys = sorted(params_with_headers.keys()) values = list(map(params_with_headers.get, keys)) - querystring = urlencode(list(zip(keys, values))) + querystring = urlencode(list(zip(keys, values))).replace('+', '%20') amzdate = timenow.strftime('%Y%m%dT%H%M%SZ') datestamp = timenow.strftime('%Y%m%d') From 19f5d7e4e90dec9afd804278214d5ba8e741fd32 Mon Sep 17 00:00:00 2001 From: Dmitri Muntean Date: Tue, 21 Apr 2015 15:46:15 +1000 Subject: [PATCH 31/47] Remove duplicate and unused code in aws utils package The code for canonical request and payload hash generation is duplicate. timestamp is not used at all. --- salt/utils/aws.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/salt/utils/aws.py b/salt/utils/aws.py index 617ebb1de3..b45593b38c 100644 --- a/salt/utils/aws.py +++ b/salt/utils/aws.py @@ -146,7 +146,6 @@ def sig4(method, endpoint, params, prov_dict, aws_api_version, location, http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html ''' timenow = datetime.datetime.utcnow() - timestamp = timenow.strftime('%Y-%m-%dT%H:%M:%SZ') # Retrieve access credentials from meta-data, or use provided access_key_id, secret_access_key, token = creds(prov_dict) @@ -159,7 +158,6 @@ def sig4(method, endpoint, params, prov_dict, aws_api_version, location, amzdate = timenow.strftime('%Y%m%dT%H%M%SZ') datestamp = timenow.strftime('%Y%m%d') - payload_hash = hashlib.sha256('').hexdigest() canonical_headers = 'host:{0}\nx-amz-date:{1}\n'.format( endpoint, @@ -167,11 +165,6 @@ def sig4(method, endpoint, params, prov_dict, aws_api_version, location, ) signed_headers = 'host;x-amz-date' - request = '\n'.join(( - method, endpoint, querystring, canonical_headers, - signed_headers, payload_hash - )) - algorithm = 'AWS4-HMAC-SHA256' # Create payload hash (hash of the request body content). For GET From 7674fee99f52e8f5d1a7db2879b555c0226262b1 Mon Sep 17 00:00:00 2001 From: Jayesh Kariya Date: Tue, 21 Apr 2015 17:30:48 +0530 Subject: [PATCH 32/47] adding states/aws_sqs unit test case --- tests/unit/states/aws_sqs_test.py | 85 +++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/unit/states/aws_sqs_test.py diff --git a/tests/unit/states/aws_sqs_test.py b/tests/unit/states/aws_sqs_test.py new file mode 100644 index 0000000000..4d810cdc18 --- /dev/null +++ b/tests/unit/states/aws_sqs_test.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import aws_sqs + +aws_sqs.__salt__ = {} +aws_sqs.__opts__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class AwsSqsTestCase(TestCase): + ''' + Test cases for salt.states.aws_sqs + ''' + # 'exists' function tests: 1 + + def test_exists(self): + ''' + Test to ensure the SQS queue exists. + ''' + name = 'myqueue' + region = 'eu-west-1' + + ret = {'name': name, + 'result': None, + 'changes': {}, + 'comment': ''} + + mock = MagicMock(side_effect=[False, True]) + with patch.dict(aws_sqs.__salt__, {'aws_sqs.queue_exists': mock}): + comt = 'AWS SQS queue {0} is set to be created'.format(name) + ret.update({'comment': comt}) + with patch.dict(aws_sqs.__opts__, {'test': True}): + self.assertDictEqual(aws_sqs.exists(name, region), ret) + + comt = u'{0} exists in {1}'.format(name, region) + ret.update({'comment': comt, 'result': True}) + self.assertDictEqual(aws_sqs.exists(name, region), ret) + + # 'absent' function tests: 1 + + def test_absent(self): + ''' + Test to remove the named SQS queue if it exists. + ''' + name = 'myqueue' + region = 'eu-west-1' + + ret = {'name': name, + 'result': None, + 'changes': {}, + 'comment': ''} + + mock = MagicMock(side_effect=[True, False]) + with patch.dict(aws_sqs.__salt__, {'aws_sqs.queue_exists': mock}): + comt = 'AWS SQS queue {0} is set to be removed'.format(name) + ret.update({'comment': comt}) + with patch.dict(aws_sqs.__opts__, {'test': True}): + self.assertDictEqual(aws_sqs.absent(name, region), ret) + + comt = u'{0} does not exist in {1}'.format(name, region) + ret.update({'comment': comt, 'result': True}) + self.assertDictEqual(aws_sqs.absent(name, region), ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(AwsSqsTestCase, needs_daemon=False) From 4aac4904b47595e5350d1d3ed31ac5267216dcb8 Mon Sep 17 00:00:00 2001 From: Jayesh Kariya Date: Tue, 21 Apr 2015 17:32:30 +0530 Subject: [PATCH 33/47] adding states/blockdev unit test case --- tests/unit/states/blockdev_test.py | 108 +++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tests/unit/states/blockdev_test.py diff --git a/tests/unit/states/blockdev_test.py b/tests/unit/states/blockdev_test.py new file mode 100644 index 0000000000..605b06d261 --- /dev/null +++ b/tests/unit/states/blockdev_test.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' +# Import Python libs +from __future__ import absolute_import +import os + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import blockdev +import salt.utils + +blockdev.__salt__ = {} +blockdev.__opts__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class BlockdevTestCase(TestCase): + ''' + Test cases for salt.states.blockdev + ''' + # 'tuned' function tests: 1 + + def test_tuned(self): + ''' + Test to manage options of block device + ''' + name = '/dev/vg/master-data' + + ret = {'name': name, + 'result': True, + 'changes': {}, + 'comment': ''} + + comt = ('Changes to {0} cannot be applied. ' + 'Not a block device. ').format(name) + with patch.dict(blockdev.__salt__, {'file.is_blkdev': False}): + ret.update({'comment': comt}) + self.assertDictEqual(blockdev.tuned(name), ret) + + comt = ('Changes to {0} will be applied '.format(name)) + with patch.dict(blockdev.__salt__, {'file.is_blkdev': True}): + ret.update({'comment': comt, 'result': None}) + with patch.dict(blockdev.__opts__, {'test': True}): + self.assertDictEqual(blockdev.tuned(name), ret) + + # 'formatted' function tests: 1 + + def test_formatted(self): + ''' + Test to manage filesystems of partitions. + ''' + name = '/dev/vg/master-data' + + ret = {'name': name, + 'result': False, + 'changes': {}, + 'comment': ''} + + with patch.object(os.path, 'exists', MagicMock(side_effect=[False, True, + True, True, + True])): + comt = ('{0} does not exist'.format(name)) + ret.update({'comment': comt}) + self.assertDictEqual(blockdev.formatted(name), ret) + + mock = MagicMock(return_value='ext4') + with patch.dict(blockdev.__salt__, {'cmd.run': mock}): + comt = ('{0} already formatted with '.format(name)) + ret.update({'comment': comt, 'result': True}) + self.assertDictEqual(blockdev.formatted(name, fs_type=''), ret) + + ret.update({'comment': 'Invalid fs_type: ext4', + 'result': False}) + with patch.object(salt.utils, 'which', + MagicMock(return_value=False)): + self.assertDictEqual(blockdev.formatted(name), ret) + + comt = ('Changes to {0} will be applied '.format(name)) + ret.update({'comment': comt, 'result': None}) + with patch.object(salt.utils, 'which', + MagicMock(return_value=True)): + with patch.dict(blockdev.__opts__, {'test': True}): + self.assertDictEqual(blockdev.formatted(name), ret) + + comt = ('Failed to format {0}'.format(name)) + ret.update({'comment': comt, 'result': False}) + with patch.object(salt.utils, 'which', + MagicMock(return_value=True)): + with patch.dict(blockdev.__opts__, {'test': False}): + self.assertDictEqual(blockdev.formatted(name), ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(BlockdevTestCase, needs_daemon=False) From 2cfd1a4395206589c3d25392a808cc6b50123204 Mon Sep 17 00:00:00 2001 From: Jayesh Kariya Date: Tue, 21 Apr 2015 17:36:07 +0530 Subject: [PATCH 34/47] adding postfix unit test case --- tests/unit/modules/postfix_test.py | 142 +++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 tests/unit/modules/postfix_test.py diff --git a/tests/unit/modules/postfix_test.py b/tests/unit/modules/postfix_test.py new file mode 100644 index 0000000000..d9be2b305e --- /dev/null +++ b/tests/unit/modules/postfix_test.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Jayesh Kariya ` +''' + +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.modules import postfix + +# Globals +postfix.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class PostfixTestCase(TestCase): + ''' + Test cases for salt.modules.postfix + ''' + def test_show_master(self): + ''' + Test for return a dict of active config values + ''' + with patch.object(postfix, '_parse_master', + return_value=({'A': 'a'}, ['b'])): + self.assertDictEqual(postfix.show_master('path'), {'A': 'a'}) + + def test_set_master(self): + ''' + Test for set a single config value in the master.cf file + ''' + with patch.object(postfix, '_parse_master', + return_value=({'A': 'a'}, ['b'])): + with patch.object(postfix, '_write_conf', return_value=None): + self.assertTrue(postfix.set_master('a', 'b')) + + def test_show_main(self): + ''' + Test for return a dict of active config values + ''' + with patch.object(postfix, '_parse_main', + return_value=({'A': 'a'}, ['b'])): + self.assertDictEqual(postfix.show_main('path'), {'A': 'a'}) + + def test_set_main(self): + ''' + Test for set a single config value in the master.cf file + ''' + with patch.object(postfix, '_parse_main', + return_value=({'A': 'a'}, ['b'])): + with patch.object(postfix, '_write_conf', return_value=None): + self.assertTrue(postfix.set_main('key', 'value')) + + def test_show_queue(self): + ''' + Test for show contents of the mail queue + ''' + with patch.dict(postfix.__salt__, {'cmd.run': + MagicMock(return_value='A\nB')}): + self.assertEqual(postfix.show_queue(), []) + + def test_delete(self): + ''' + Test for delete message(s) from the mail queue + ''' + with patch.object(postfix, 'show_queue', return_value={}): + self.assertDictEqual(postfix.delete('queue_id'), + {'result': False, 'message': + 'No message in queue with ID queue_id'}) + + with patch.dict(postfix.__salt__, + {'cmd.run_all': + MagicMock(return_value={'retcode': 0})}): + self.assertDictEqual(postfix.delete('ALL'), + {'result': True, 'message': + 'Successfully removed all messages'}) + + def test_hold(self): + ''' + Test for set held message(s) in the mail queue to unheld + ''' + with patch.object(postfix, 'show_queue', return_value={}): + self.assertDictEqual(postfix.hold('queue_id'), + {'result': False, 'message': + 'No message in queue with ID queue_id'}) + + with patch.dict(postfix.__salt__, + {'cmd.run_all': + MagicMock(return_value={'retcode': 0})}): + self.assertDictEqual(postfix.hold('ALL'), + {'result': True, 'message': + 'Successfully placed all messages on hold'}) + + def test_unhold(self): + ''' + Test for put message(s) on hold from the mail queue + ''' + with patch.object(postfix, 'show_queue', return_value={}): + self.assertDictEqual(postfix.unhold('queue_id'), + {'result': False, 'message': + 'No message in queue with ID queue_id'}) + + with patch.dict(postfix.__salt__, + {'cmd.run_all': + MagicMock(return_value={'retcode': 0})}): + self.assertDictEqual(postfix.unhold('ALL'), + {'result': True, 'message': + 'Successfully set all message as unheld'}) + + def test_requeue(self): + ''' + Test for requeue message(s) in the mail queue + ''' + with patch.object(postfix, 'show_queue', return_value={}): + self.assertDictEqual(postfix.requeue('queue_id'), + {'result': False, 'message': + 'No message in queue with ID queue_id'}) + + with patch.dict(postfix.__salt__, + {'cmd.run_all': + MagicMock(return_value={'retcode': 0})}): + self.assertDictEqual(postfix.requeue('ALL'), + {'result': True, 'message': + 'Successfully requeued all messages'}) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(PostfixTestCase, needs_daemon=False) From 03ae55c2849750d0916d9466e93f8f2b76811fb1 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sun, 22 Feb 2015 19:22:08 -0800 Subject: [PATCH 35/47] use `is` instead of `==` for class equality checks ************* Module salt.pillar.pepa /Users/chris/code/salt/salt/pillar/pepa.py:460: [W1504(unidiomatic-typecheck), ext_pillar] Using type() instead of isinstance() for a typecheck. ************* Module salt.utils.process /Users/chris/code/salt/salt/utils/process.py:225: [W1504(unidiomatic-typecheck), ProcessManager.add_process] Using type() instead of isinstance() for a typecheck. --- salt/pillar/pepa.py | 2 +- salt/utils/process.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/salt/pillar/pepa.py b/salt/pillar/pepa.py index 68989ca9a7..847d361f94 100644 --- a/salt/pillar/pepa.py +++ b/salt/pillar/pepa.py @@ -455,7 +455,7 @@ def ext_pillar(minion_id, pillar, resource, sequence, subkey=False, subkey_only= immutable[rkey] = True if rkey not in output: log.error('Cant\'t merge key {0} doesn\'t exist'.format(rkey)) - elif type(results[key]) != type(output[rkey]): + elif type(results[key]) is not type(output[rkey]): log.error('Can\'t merge different types for key {0}'.format(rkey)) elif type(results[key]) is dict: output[rkey].update(results[key]) diff --git a/salt/utils/process.py b/salt/utils/process.py index 90301cf28c..f6565cb56e 100644 --- a/salt/utils/process.py +++ b/salt/utils/process.py @@ -229,17 +229,17 @@ class ProcessManager(object): if kwargs is None: kwargs = {} - if type(multiprocessing.Process) == type(tgt) and issubclass(tgt, multiprocessing.Process): - p = tgt(*args, **kwargs) + if type(multiprocessing.Process) is type(tgt) and issubclass(tgt, multiprocessing.Process): + process = tgt(*args, **kwargs) else: - p = multiprocessing.Process(target=tgt, args=args, kwargs=kwargs) + process = multiprocessing.Process(target=tgt, args=args, kwargs=kwargs) - p.start() - log.debug("Started '{0}' with pid {1}".format(tgt.__name__, p.pid)) - self._process_map[p.pid] = {'tgt': tgt, - 'args': args, - 'kwargs': kwargs, - 'Process': p} + process.start() + log.debug("Started '{0}' with pid {1}".format(tgt.__name__, process.pid)) + self._process_map[process.pid] = {'tgt': tgt, + 'args': args, + 'kwargs': kwargs, + 'Process': process} def restart_process(self, pid): ''' From cb50a7dcc3438715c8d4343c5f0f71de18796141 Mon Sep 17 00:00:00 2001 From: Chris Rebert Date: Sun, 22 Feb 2015 19:21:10 -0800 Subject: [PATCH 36/47] use isinstance() instead of type() for typechecks ************* Module salt.modules.mdadm /Users/chris/code/salt/salt/modules/mdadm.py:340: [W1504(unidiomatic-typecheck), assemble] Using type() instead of isinstance() for a typecheck. ************* Module salt.states.pip_state /Users/chris/code/salt/salt/states/pip_state.py:476: [W1504(unidiomatic-typecheck), installed.] Using type() instead of isinstance() for a typecheck. ************* Module salt.states.pkg /Users/chris/code/salt/salt/states/pkg.py:227: [W1504(unidiomatic-typecheck), _find_install_targets] Using type() instead of isinstance() for a typecheck. ************* Module salt.pillar.pepa /Users/chris/code/salt/salt/pillar/pepa.py:462: [W1504(unidiomatic-typecheck), ext_pillar] Using type() instead of isinstance() for a typecheck. /Users/chris/code/salt/salt/pillar/pepa.py:464: [W1504(unidiomatic-typecheck), ext_pillar] Using type() instead of isinstance() for a typecheck. --- salt/modules/mdadm.py | 2 +- salt/pillar/pepa.py | 4 ++-- salt/states/pip_state.py | 7 ++++--- salt/states/pkg.py | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/salt/modules/mdadm.py b/salt/modules/mdadm.py index be4e73ee84..207459e3f8 100644 --- a/salt/modules/mdadm.py +++ b/salt/modules/mdadm.py @@ -341,7 +341,7 @@ def assemble(name, opts.append(kwargs[key]) # Devices may have been written with a blob: - if type(devices) is str: + if isinstance(devices, str): devices = devices.split(',') cmd = ['mdadm', '-A', name, '-v', opts] + devices diff --git a/salt/pillar/pepa.py b/salt/pillar/pepa.py index 847d361f94..588e5759f5 100644 --- a/salt/pillar/pepa.py +++ b/salt/pillar/pepa.py @@ -457,9 +457,9 @@ def ext_pillar(minion_id, pillar, resource, sequence, subkey=False, subkey_only= log.error('Cant\'t merge key {0} doesn\'t exist'.format(rkey)) elif type(results[key]) is not type(output[rkey]): log.error('Can\'t merge different types for key {0}'.format(rkey)) - elif type(results[key]) is dict: + elif isinstance(results[key], dict): output[rkey].update(results[key]) - elif type(results[key]) is list: + elif isinstance(results[key], list): output[rkey].extend(results[key]) else: log.error('Unsupported type need to be list or dict for key {0}'.format(rkey)) diff --git a/salt/states/pip_state.py b/salt/states/pip_state.py index ccc351f852..228e181df0 100644 --- a/salt/states/pip_state.py +++ b/salt/states/pip_state.py @@ -18,9 +18,9 @@ requisite to a pkg.installed state for the package which provides pip - require: - pkg: python-pip ''' -from __future__ import absolute_import # Import python libs +from __future__ import absolute_import import logging # Import salt libs @@ -29,6 +29,7 @@ from salt.version import SaltStackVersion as _SaltStackVersion from salt.exceptions import CommandExecutionError, CommandNotFoundError # Import 3rd-party libs +import salt.ext.six as six try: import pip HAS_PIP = True @@ -470,8 +471,8 @@ def installed(name, # prepro = lambda pkg: pkg if type(pkg) == str else \ # ' '.join((pkg.items()[0][0], pkg.items()[0][1].replace(',', ';'))) # pkgs = ','.join([prepro(pkg) for pkg in pkgs]) - prepro = lambda pkg: pkg if type(pkg) == str else \ - ' '.join((pkg.items()[0][0], pkg.items()[0][1])) + prepro = lambda pkg: pkg if isinstance(pkg, str) else \ + ' '.join((six.iteritems(pkg)[0][0], six.iteritems(pkg)[0][1])) pkgs = [prepro(pkg) for pkg in pkgs] ret = {'name': ';'.join(pkgs), 'result': None, diff --git a/salt/states/pkg.py b/salt/states/pkg.py index ec3b524b5a..b9fb54449a 100644 --- a/salt/states/pkg.py +++ b/salt/states/pkg.py @@ -221,7 +221,7 @@ def _find_install_targets(name=None, # Get the ignore_types list if any from the pkg_verify argument if isinstance(pkg_verify, list) and any(x.get('ignore_types') is not None for x in pkg_verify - if type(x) is _OrderedDict + if isinstance(x, _OrderedDict) and 'ignore_types' in x): ignore_types = next(x.get('ignore_types') for x in pkg_verify From 8a0e042787b3e8e419d48e7fec53631e6f27105c Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Tue, 21 Apr 2015 13:49:45 +0100 Subject: [PATCH 37/47] Fix PyLint W1504(unidiomatic-typecheck) Using type() instead of isinstance() for a typecheck. --- salt/pillar/pepa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/pillar/pepa.py b/salt/pillar/pepa.py index 588e5759f5..fb5809b7b2 100644 --- a/salt/pillar/pepa.py +++ b/salt/pillar/pepa.py @@ -455,7 +455,7 @@ def ext_pillar(minion_id, pillar, resource, sequence, subkey=False, subkey_only= immutable[rkey] = True if rkey not in output: log.error('Cant\'t merge key {0} doesn\'t exist'.format(rkey)) - elif type(results[key]) is not type(output[rkey]): + elif not isinstance(results[key], type(output[rkey])): log.error('Can\'t merge different types for key {0}'.format(rkey)) elif isinstance(results[key], dict): output[rkey].update(results[key]) From 3d058759dcaa363aefc858f82a224bca9be18818 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Tue, 21 Apr 2015 13:52:13 +0100 Subject: [PATCH 38/47] Ignore no name in module for distutils.version --- salt/pillar/ec2_pillar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/salt/pillar/ec2_pillar.py b/salt/pillar/ec2_pillar.py index 23996e16c7..783ec87a98 100644 --- a/salt/pillar/ec2_pillar.py +++ b/salt/pillar/ec2_pillar.py @@ -26,12 +26,12 @@ returns a list of key/value pairs for all of the EC2 tags assigned to the instance. ''' -from __future__ import absolute_import # Import python libs +from __future__ import absolute_import import re import logging -from distutils.version import StrictVersion +from distutils.version import StrictVersion # pylint: disable=no-name-in-module # Import AWS Boto libs try: From a37502f0f66bea3f35b4918d6d484693fee37a31 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Tue, 21 Apr 2015 13:54:21 +0100 Subject: [PATCH 39/47] Remove bad PyLint options --- salt/cloud/clouds/gce.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/salt/cloud/clouds/gce.py b/salt/cloud/clouds/gce.py index dbc0998912..e9e582cfa5 100644 --- a/salt/cloud/clouds/gce.py +++ b/salt/cloud/clouds/gce.py @@ -2075,7 +2075,7 @@ def create(vm_=None, call=None): ) try: - node_data = conn.create_node(**kwargs) # pylint: disable=W0142 + node_data = conn.create_node(**kwargs) except Exception as exc: # pylint: disable=W0703 log.error( 'Error creating {0} on GCE\n\n' @@ -2171,7 +2171,6 @@ def create(vm_=None, call=None): transport=__opts__['transport'] ) - # pylint: disable=W0142 deployed = salt.utils.cloud.deploy_script(**deploy_kwargs) if deployed: log.info('Salt installed on {0}'.format(vm_['name'])) From 3dc9b0129b71755b2f756cfbd7f650d3154979a5 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Tue, 21 Apr 2015 14:00:00 +0100 Subject: [PATCH 40/47] Fix comparison to None --- salt/modules/file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/modules/file.py b/salt/modules/file.py index 695df7ca69..19d2d2f2f6 100644 --- a/salt/modules/file.py +++ b/salt/modules/file.py @@ -1344,7 +1344,7 @@ def replace(path, raise CommandExecutionError("Exception: {0}".format(exc)) if not found and (append_if_not_found or prepend_if_not_found): - if None == not_found_content: + if not_found_content is None: not_found_content = repl if prepend_if_not_found: new_file.insert(0, not_found_content + '\n') From 7f1954bea9909cfadae4e3261f084bc9e0409ac0 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Tue, 21 Apr 2015 14:00:46 +0100 Subject: [PATCH 41/47] Fix False comparison --- salt/modules/zcbuildout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/modules/zcbuildout.py b/salt/modules/zcbuildout.py index f2514e9711..a77860ccb3 100644 --- a/salt/modules/zcbuildout.py +++ b/salt/modules/zcbuildout.py @@ -736,7 +736,7 @@ def bootstrap(directory='.', with salt.utils.fopen(b_py) as fic: content = fic.read() if ( - (False != test_release) + (test_release is not False) and ' --accept-buildout-test-releases' in content ): bootstrap_args += ' --accept-buildout-test-releases' From ae2bca1f6ffb3a0864e69b2ef0f1178387986248 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Tue, 21 Apr 2015 14:02:17 +0100 Subject: [PATCH 42/47] Fix W1504(unidiomatic-typecheck --- tests/integration/modules/decorators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/modules/decorators.py b/tests/integration/modules/decorators.py index 5815624f0c..4f35d0941d 100644 --- a/tests/integration/modules/decorators.py +++ b/tests/integration/modules/decorators.py @@ -19,7 +19,7 @@ class DecoratorTest(integration.ModuleCase): def not_test_depends(self): ret = self.run_function('runtests_decorators.depends') self.assertTrue(ret['ret']) - self.assertTrue(type(ret['time']) == float) + self.assertTrue(isinstance(ret['time'], float)) def test_missing_depends(self): self.assertIn( @@ -46,7 +46,7 @@ class DecoratorTest(integration.ModuleCase): def not_test_depends_will_fallback(self): ret = self.run_function('runtests_decorators.depends_will_fallback') self.assertTrue(ret['ret']) - self.assertTrue(type(ret['time']) == float) + self.assertTrue(isinstance(ret['time'], float)) def test_missing_depends_again(self): self.assertIn( From 4002d90e19d2c5c3e4190ad78cca58d5e5e5ad8b Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Tue, 21 Apr 2015 14:51:54 +0100 Subject: [PATCH 43/47] Disable W1307 (invalid-format-index) Using invalid lookup key '%s' in format specifier "0['%s']" --- .testing.pylintrc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.testing.pylintrc b/.testing.pylintrc index 7dcf144533..056832f477 100644 --- a/.testing.pylintrc +++ b/.testing.pylintrc @@ -68,6 +68,7 @@ disable=R, W0631, W0704, W1202, + W1307, F0220, F0401, E8501, @@ -117,6 +118,7 @@ disable=R, # F0220 (unresolved-interface) # F0401 (import-error) # W1202 (logging-format-interpolation) Use % formatting in logging functions but pass the % parameters as arguments +# W1307 (invalid-format-index) Using invalid lookup key '%s' in format specifier "0['%s']" # # E812* All PEP8 E12* # E8265 PEP8 E265 - block comment should start with "# " From 8565e9fe1cce3ac14c8d6fb243decf4e08be562b Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Tue, 21 Apr 2015 14:53:21 +0100 Subject: [PATCH 44/47] Ignore no name in module for distutils.version --- salt/netapi/rest_tornado/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/salt/netapi/rest_tornado/__init__.py b/salt/netapi/rest_tornado/__init__.py index 153b649217..47cf506417 100644 --- a/salt/netapi/rest_tornado/__init__.py +++ b/salt/netapi/rest_tornado/__init__.py @@ -1,11 +1,9 @@ # encoding: utf-8 -from __future__ import print_function - -from __future__ import absolute_import +from __future__ import absolute_import, print_function import hashlib import logging -import distutils.version +import distutils.version # pylint: disable=no-name-in-module __virtualname__ = 'rest_tornado' From 3ae68c4f7be09df070c2b2d006d5d680eaace59f Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Tue, 21 Apr 2015 15:03:47 +0100 Subject: [PATCH 45/47] Minor cosmetic changes in order not to disable the PEP8 PyLint code all together --- .testing.pylintrc | 1 + tests/unit/modules/zcbuildout_test.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.testing.pylintrc b/.testing.pylintrc index 056832f477..2a22495c0d 100644 --- a/.testing.pylintrc +++ b/.testing.pylintrc @@ -122,6 +122,7 @@ disable=R, # # E812* All PEP8 E12* # E8265 PEP8 E265 - block comment should start with "# " +# E8266 PEP8 E266 - too many leading '#' for block comment # E8501 PEP8 line too long # E8402 module level import not at top of file # E8731 do not assign a lambda expression, use a def diff --git a/tests/unit/modules/zcbuildout_test.py b/tests/unit/modules/zcbuildout_test.py index 39abcd7367..d58d28b7e2 100644 --- a/tests/unit/modules/zcbuildout_test.py +++ b/tests/unit/modules/zcbuildout_test.py @@ -151,11 +151,11 @@ class BuildoutTestCase(Base): # These lines are throwing pylint errors - disabling for now since we are skipping # these tests #self.assertTrue( - # u'' - # u'OUTPUT:\n' - # u'foo\n' - # u'' - #in ret1['outlog'] + # u'' + # u'OUTPUT:\n' + # u'foo\n' + # u'' + # in ret1['outlog'] #) # These lines are throwing pylint errors - disabling for now since we are skipping @@ -164,11 +164,11 @@ class BuildoutTestCase(Base): # These lines are throwing pylint errors - disabling for now since we are skipping # these tests # self.assertTrue( - # u'INFO: ibar\n' - # u'WARN: wbar\n' - # u'DEBUG: dbar\n' - # u'ERROR: ebar\n' - #in ret1['outlog'] + # u'INFO: ibar\n' + # u'WARN: wbar\n' + # u'DEBUG: dbar\n' + # u'ERROR: ebar\n' + # in ret1['outlog'] #) # These lines are throwing pylint errors - disabling for now since we are skipping # these tests From 62ae9e061a8398195f3effe689a411f5a1eb23d8 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Tue, 21 Apr 2015 15:08:42 +0100 Subject: [PATCH 46/47] Forgot to add the code --- .testing.pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.testing.pylintrc b/.testing.pylintrc index 2a22495c0d..3d75bc13db 100644 --- a/.testing.pylintrc +++ b/.testing.pylintrc @@ -83,6 +83,7 @@ disable=R, E8129, E8131, E8265, + E8266, E8402, E8731 From 7899914dbe04fe74e27196c74b15e5210c7db85d Mon Sep 17 00:00:00 2001 From: Justin Findlay Date: Mon, 20 Apr 2015 21:55:21 -0600 Subject: [PATCH 47/47] fix deprecated artifactory state code --- salt/states/artifactory.py | 4 ++-- tests/unit/states/artifactory_test.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/salt/states/artifactory.py b/salt/states/artifactory.py index cd5a8d554d..00b55c51ae 100644 --- a/salt/states/artifactory.py +++ b/salt/states/artifactory.py @@ -75,8 +75,8 @@ def downloaded(name, artifact, target_dir='/tmp', target_file=None): log.debug("ret=%s", str(ret)) return ret - except Exception as e: - return None, e.message + except Exception as exception: + return None, exception def __fetch_from_artifactory(artifact, target_dir, target_file): diff --git a/tests/unit/states/artifactory_test.py b/tests/unit/states/artifactory_test.py index 13fcb92169..b5d8280a61 100644 --- a/tests/unit/states/artifactory_test.py +++ b/tests/unit/states/artifactory_test.py @@ -54,8 +54,9 @@ class ArtifactoryTestCase(TestCase): with patch.object(artifactory, '__fetch_from_artifactory', MagicMock(side_effect=Exception('error'))): - self.assertEqual(artifactory.downloaded(name, artifact)['comment'] - .message, 'error') + ret = artifactory.downloaded(name, artifact) + self.assertEqual(ret[0], None) + self.assertEqual(repr(ret[1]), repr(Exception('error'))) if __name__ == '__main__':