diff --git a/doc/topics/development/hacking.rst b/doc/topics/development/hacking.rst index 2c5fa33a603..309cb6ffd1b 100644 --- a/doc/topics/development/hacking.rst +++ b/doc/topics/development/hacking.rst @@ -215,6 +215,48 @@ If you would like to log to the console instead of to the log file, remove the ` instructions. +Changing Default Paths +~~~~~~~~~~~~~~~~~~~~~~ + +Instead of updating your configuration files to point to the new root directory +and having to pass the new configuration directory path to all of Salt's CLI +tools, you can explicitly tweak the default system paths that Salt expects: + +.. code-block:: bash + + GENERATE_SALT_SYSPATHS=1 pip --global-option='--salt-root-dir=/path/to/your/virtualenv/' \ + install -e ./salt # the path to the salt git clone from above + + +You can now call all of Salt's CLI tools without explicitly passing the configuration directory. + +Additional Options +.................. + +In case you want to distribute your virtualenv, you probably don't want to +include Salt's clone ``.git/`` directory, and, without it, Salt won't report +the accurate version. You can tell ``setup.py`` to generate the hardcoded +version information which is distributable: + +.. code-block:: bash + + GENERATE_SALT_SYSPATHS=1 WRITE_SALT_VERSION=1 pip --global-option='--salt-root-dir=/path/to/your/virtualenv/' \ + install -e ./salt # the path to the salt git clone from above + + +Instead of passing those two environmental variables, you can just pass a +single one which will trigger the other two: + +.. code-block:: bash + + MIMIC_SALT_INSTALL=1 pip --global-option='--salt-root-dir=/path/to/your/virtualenv/' \ + install -e ./salt # the path to the salt git clone from above + + +This last one will grant you an edditable salt installation with hardcoded +system paths and version information. + + Installing Salt from the Python Package Index --------------------------------------------- diff --git a/doc/topics/installation/windows.rst b/doc/topics/installation/windows.rst index 2473fdbb5f7..76ad766fefc 100644 --- a/doc/topics/installation/windows.rst +++ b/doc/topics/installation/windows.rst @@ -139,8 +139,8 @@ Install the following software: Download the Prerequisite zip file for your CPU architecture from the SaltStack download site: -* `Salt32.zip `_ -* `Salt64.zip `_ +* `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``. diff --git a/doc/topics/releases/2015.5.4.rst b/doc/topics/releases/2015.5.4.rst index 4965356aa23..23695bec73a 100644 --- a/doc/topics/releases/2015.5.4.rst +++ b/doc/topics/releases/2015.5.4.rst @@ -2,16 +2,1841 @@ Salt 2015.5.4 Release Notes =========================== -:release: TBA - Version 2015.5.4 is a bugfix release for :doc:`2015.5.0 `. Changes: +- The ``cron.present`` state now correctly defaults to state ID as identifier. + - When querying for VMs in ``ditigal_ocean_v2.py``, the number of VMs to include in a page was changed from 20 (default) to 200 to reduce the number of API calls to Digital Ocean. - The ``vmware`` Salt-Cloud driver was back-ported from the develop branch in order for installations of Salt that are older than 2015.8.0 to be able to use the ``vmware`` driver without stack-tracing on various deprecation paths that were implemented in the 2015.8.0 release. + +Changes for v2015.5.3..v2015.5.4 +-------------------------------- + +Extended changelog courtesy of Todd Stansell (https://github.com/tjstansell/salt-changelogs): + +*Generated at: 2015-08-13T20:23:30Z* + +Statistics: + +- Total Merges: **247** +- Total Issue references: **140** +- Total PR references: **330** + +Changes: + +- **PR** `#26292`_: (*jquast*) Rabbitmq 3.2.4 on Ubuntu has "...done.", not "...done" + @ *2015-08-13T19:53:29Z* + +- **PR** `#26296`_: (*jquast*) bugfix missing `runas=None' for rabbitmqctl cmds (backport to 2015.5) + @ *2015-08-13T19:52:40Z* + +- **PR** `#26293`_: (*jfindlay*) Fix `#26268`_ + @ *2015-08-13T19:48:06Z* + + - **ISSUE** `#25618`_: (*twangboy*) Fix reg.py to work with the registry properly + | refs: `#26268`_ + - **PR** `#26268`_: (*twangboy*) Multiple improvements to reg executionmod and state mod + | refs: `#26293`_ + +- **PR** `#26290`_: (*rallytime*) Only call convert_to_arn when action name is provided + @ *2015-08-13T18:48:58Z* + + - **ISSUE** `#25192`_: (*deuscapturus*) 2015.5.2 boto_cloudwatch_alarm.present not working. + | refs: `#26290`_ + +- **PR** `#26288`_: (*bbinet*) allow to delete grains which value is False + @ *2015-08-13T18:24:36Z* + +- **PR** `#26263`_: (*rallytime*) Don't make changes when test=True for openstack present/absent funcs + @ *2015-08-13T16:30:31Z* + + - **ISSUE** `#24882`_: (*nmadhok*) salt.states.openstack_config.present and salt.states.openstack_config.absent make changes when test=True + | refs: `#26263`_ + +- **PR** `#26265`_: (*rallytime*) Don't stacktrace on query return in ec2.create_snapshot + @ *2015-08-13T16:28:48Z* + + - **ISSUE** `#24484`_: (*codehotter*) clouds/ec2.py: create_snapshot throws exception + | refs: `#26265`_ + +- **PR** `#26285`_: (*stanislavb*) Remove explicit version from instance identity URL + @ *2015-08-13T16:25:32Z* + +- **PR** `#26275`_: (*cachedout*) Re-init modules on multi-master reconnect + @ *2015-08-13T15:52:50Z* + +- **PR** `#26273`_: (*garethgreenaway*) Fixes to schedule module in 2015.5 + @ *2015-08-13T15:34:43Z* + +- **PR** `#26271`_: (*rallytime*) Fix del_root_vol_on_destroy and del_all_vols_on_destroy functionality on ec2 + @ *2015-08-12T23:22:47Z* + + - **ISSUE** `#24483`_: (*codehotter*) clouds/ec2.py: del_root_vol_on_destroy and del_all_vols_on_destroy not working + | refs: `#26271`_ + +- **PR** `#26219`_: (*anlutro*) cron: make identifier default to state ID + @ *2015-08-12T18:42:33Z* + + - **ISSUE** `#25958`_: (*anlutro*) Cron identifier does not default to state ID as documented + | refs: `#26219`_ + +- **PR** `#26257`_: (*rallytime*) Back-port `#26237`_ to 2015.5 + @ *2015-08-12T18:40:35Z* + + - **ISSUE** `#26207`_: (*fullermd*) group members setting fails with obscure error message on FreeBSD + | refs: `#26237`_ + - **PR** `#26237`_: (*silenius*) fix issue `#26207`_ + | refs: `#26257`_ + +- **PR** `#26258`_: (*nmadhok*) Fix permission on tests/runtests.py on 2015.5 branch + @ *2015-08-12T18:40:04Z* + +- **PR** `#26261`_: (*nmadhok*) Correct spelling of integration in docs + @ *2015-08-12T18:14:48Z* + + - **PR** `#2015`_: (*thekuffs*) Esky / bbfreeze support + +- **PR** `#26247`_: (*nmadhok*) Initial commit of unit tests for vmware cloud driver + @ *2015-08-12T16:58:24Z* + +- **PR** `#26246`_: (*nmadhok*) Backport additions to VMware cloud driver from develop to 2015.5 branch + @ *2015-08-12T15:11:26Z* + +- **PR** `#26239`_: (*opdude*) Fixed documentation to match function name + @ *2015-08-12T14:48:52Z* + +- **PR** `#26232`_: (*garethgreenaway*) Fix to trust_key in gpg module for 2015.5. + @ *2015-08-12T04:48:27Z* + +- **PR** `#26084`_: (*twangboy*) Added python_shell=True, quoted user input + @ *2015-08-10T21:29:35Z* + + - **ISSUE** `#25802`_: (*jefftucker*) Running module "npm.list" fails on Windows for masterless minion + | refs: `#26084`_ + +- **PR** `#26183`_: (*cro*) Fix LDAP configuration issue. + @ *2015-08-10T19:09:41Z* + +- **PR** `#26186`_: (*jacobhammons*) regenerated man pages + @ *2015-08-10T19:07:44Z* + +- **PR** `#26182`_: (*basepi*) [2015.5] Merge forward from 2014.7 to 2015.5 + @ *2015-08-10T19:00:10Z* + + - **ISSUE** `#25961`_: (*getabc*) [2015.5.3-2] salt-winrepo.git/salt-minion.sls fails certificate '*.wpengine.com' or 'wpengine.com' + | refs: `#26047`_ + - **ISSUE** `#25751`_: (*basepi*) Document `master_finger` more prominently + | refs: `#26088`_ + - **PR** `#26116`_: (*corux*) file.replace fails if repl string is an invalid regex and append/prepend is used + - **PR** `#26088`_: (*jacobhammons*) Master finger + - **PR** `#26047`_: (*jacobhammons*) Updated windows download links in the docs to https://repo.saltstack.com + +- **PR** `#26000`_: (*driskell*) Implement full event caching for subscribed tags + @ *2015-08-10T18:57:17Z* + + - **ISSUE** `#25998`_: (*driskell*) Event subsystem discarding required events during --batch breaking it for slow running commands + | refs: `#26000`_ + +- **PR** `#26175`_: (*rallytime*) Back-port `#26153`_ to 2015.5 + @ *2015-08-10T18:22:32Z* + + - **PR** `#26153`_: (*loa*) Fix dockerio state documentation typo + | refs: `#26175`_ + +- **PR** `#26177`_: (*rallytime*) Back-port `#26147`_ to 2015.5 + @ *2015-08-10T18:22:01Z* + + - **ISSUE** `#26024`_: (*jpic*) lxc_conf_unset in cloud.profile is ignored + - **PR** `#26147`_: (*martinhoefling*) Fixes `#26024`_ + | refs: `#26177`_ + +- **PR** `#26179`_: (*rallytime*) Back-port `#25404`_ to 2015.5 + @ *2015-08-10T18:21:50Z* + + - **ISSUE** `#21082`_: (*clinta*) master_type failover does not failover on DNS errors + | refs: `#25404`_ + - **PR** `#25404`_: (*DmitryKuzmenko*) Fixed minion failover to next master on DNS errors. + | refs: `#26179`_ + +- **PR** `#26180`_: (*jfindlay*) fix processing of state.template + @ *2015-08-10T18:21:38Z* + + - **ISSUE** `#26112`_: (*wt*) state.template fails with unclear error with template with only an include + | refs: `#26180`_ + +- **PR** `#26172`_: (*nmadhok*) [Backport] Make sure variable is a dictionary before popping something from it. + @ *2015-08-10T16:42:50Z* + + - **ISSUE** `#26162`_: (*nmadhok*) VMware cloud driver create function failing with traceback on latest develop + | refs: `#26163`_ `#26172`_ + - **PR** `#26163`_: (*nmadhok*) Make sure variable is a dictionary before popping something from it. + +- **PR** `#26168`_: (*cachedout*) Fix slack docs + @ *2015-08-10T14:57:18Z* + + - **ISSUE** `#26098`_: (*rdinoff*) SALT.STATES.SLACK Doc update + | refs: `#26168`_ + +- **PR** `#26127`_: (*garethgreenaway*) Fixes to salt.utils.http related to cp.get_file_str bug. + @ *2015-08-10T14:38:25Z* + + - **ISSUE** `#24106`_: (*nvx*) fileclient.py#get_url ignores HTTP Auth again (2015.5 regression) + | refs: `#26127`_ + +- **PR** `#26140`_: (*nmadhok*) VMware cloud driver fixes + @ *2015-08-10T13:15:58Z* + + - **ISSUE** `#26141`_: (*nmadhok*) salt-cloud VMware driver fails with error in parsing configuration file + | refs: `#26140`_ + - **ISSUE** `#25809`_: (*o-sleep*) vmware cloud module error message + | refs: `#26140`_ + - **ISSUE** `#25625`_: (*steverweber*) cloud vmware driver does not provide mac_address unless vmware tools is running + | refs: `#26137`_ `#26140`_ + +- **PR** `#26137`_: (*steverweber*) use device mac address if vmtools not active + @ *2015-08-09T03:05:36Z* + + - **ISSUE** `#25625`_: (*steverweber*) cloud vmware driver does not provide mac_address unless vmware tools is running + | refs: `#26137`_ `#26140`_ + +- **PR** `#26119`_: (*jodv*) Backport eauth bugfix to 2015.5 + @ *2015-08-09T02:19:52Z* + +- **PR** `#26135`_: (*cro*) Fix proxy minions in 2015.5 and significantly update documentation. + @ *2015-08-09T02:19:21Z* + +- **PR** `#26132`_: (*TheBigBear*) minor edit + @ *2015-08-08T21:05:34Z* + +- **PR** `#26133`_: (*amontalban*) Fixed `#25915`_ in salt/modules/pkgng.py and salt/states/pkg.py + @ *2015-08-08T21:05:05Z* + + - **ISSUE** `#25915`_: (*ari*) FreeBSD pkg install fails + +- **PR** `#26111`_: (*anlutro*) Better error messages when virtualenv creation fails + @ *2015-08-07T21:42:09Z* + +- **PR** `#26110`_: (*jfindlay*) check for sources before adding them to cmd str + @ *2015-08-07T21:33:23Z* + + - **ISSUE** `#26093`_: (*freedba*) archive.tar bug + | refs: `#26110`_ + +- **PR** `#26106`_: (*vr-jack*) Update __init__.py + @ *2015-08-07T21:15:55Z* + +- **PR** `#26101`_: (*rallytime*) Back-port `#25984`_ to 2015.5 + @ *2015-08-07T18:56:26Z* + + - **ISSUE** `#25983`_: (*jmdcal*) Trying to get md5 of local zip + | refs: `#25984`_ + - **PR** `#25984`_: (*jmdcal*) Support local files without md5sum + | refs: `#26101`_ + +- **PR** `#26080`_: (*techhat*) Fix string checking in s3fs + @ *2015-08-06T23:36:09Z* + +- **PR** `#26079`_: (*cachedout*) Update docs to remove state.over + @ *2015-08-06T23:35:26Z* + + - **ISSUE** `#26039`_: (*basepi*) Update scheduler docs to use orchestrate instead of overstate + | refs: `#26079`_ + +- **PR** `#26058`_: (*opdude*) Fix choco version on chocolatey versions below 0.9.9 + @ *2015-08-06T18:50:10Z* + +- **PR** `#26068`_: (*jfindlay*) fix autoruns.list looking in wrong directory + @ *2015-08-06T18:49:48Z* + +- **PR** `#26065`_: (*s0undt3ch*) [2015.5] Update to latest bootstrap stable release v2015.06.08 + @ *2015-08-06T17:09:35Z* + + - **ISSUE** `#634`_: (*loupgaroublond*) /srv/salt/_grains/ not documented + | refs: `#26065`_ + - **ISSUE** `#631`_: (*fatbox*) Can't extend the same item multiple times + | refs: `#26065`_ + - **ISSUE** `#625`_: (*whiteinge*) `cmd.run` state `user` flag is not working + | refs: `#25506`_ `#632`_ + - **PR** `#640`_: (*terminalmage*) fix syntax errors introduced in 0f776c13 + | refs: `#26065`_ + - **PR** `#638`_: (*blast-hardcheese*) Tightened up configuration documentation + | refs: `#26065`_ + - **PR** `#633`_: (*epoelke*) Bug fix to salt-key + | refs: `#26065`_ + - **PR** `#632`_: (*whiteinge*) Change the ``cmd.run`` state to use the new ``runas`` arg + | refs: `#26065`_ + +- **PR** `#26061`_: (*gmcwhistler*) Patch for issue `#25994`_ + @ *2015-08-06T17:07:34Z* + + - **ISSUE** `#25994`_: (*gmcwhistler*) module.ilo tempfile creation in __execute_cmd results in TypeError: cannot concatenate 'str' and 'int' objects + +- **PR** `#26064`_: (*s0undt3ch*) Don't stacktrace when trying to get the default locale. + @ *2015-08-06T16:11:05Z* + + - **ISSUE** `#26063`_: (*saltstack-bot*) not working with salt-cloud shows unknown locale error + | refs: `#26064`_ + +- **PR** `#26048`_: (*jacobhammons*) Updated windows download links in the docs to https://repo.saltstack.com + @ *2015-08-05T22:59:50Z* + +- **PR** `#26044`_: (*rallytime*) Make sure the key we're comparing is also lowercase + @ *2015-08-05T19:23:54Z* + + - **ISSUE** `#25616`_: (*rallytime*) [2015.5] Provisioning Linodes Stacktraces + | refs: `#26044`_ + +- **PR** `#26042`_: (*jfindlay*) fix test mode logic in state docs + @ *2015-08-05T19:23:07Z* + +- **PR** `#26036`_: (*nicholascapo*) survey.hash: Remove manually printed text + @ *2015-08-05T19:21:59Z* + + - **ISSUE** `#24460`_: (*nicholascapo*) Survey runner does not follow `--out` flag + | refs: `#26036`_ + +- **PR** `#26030`_: (*opdude*) Fix a bug in choco version that returned odd data + @ *2015-08-05T16:30:25Z* + +- **PR** `#26032`_: (*jfindlay*) add test logic to state reult doc + @ *2015-08-05T16:28:32Z* + +- **PR** `#26031`_: (*alekti*) Revert "Add file as supported protocol for file source_hash. Fixes `#23764`_" + @ *2015-08-05T15:32:01Z* + + - **ISSUE** `#23764`_: (*es1o*) source_hash from local file is not supported. + | refs: `#25750`_ + +- **PR** `#26021`_: (*anlutro*) Documentation: Specify versionadded for git.present shared argument + @ *2015-08-05T14:17:38Z* + +- **PR** `#26020`_: (*alekti*) Correctly resolve conflict merging pull 25750 to 2015.5 + @ *2015-08-05T14:16:58Z* + + - **ISSUE** `#23764`_: (*es1o*) source_hash from local file is not supported. + | refs: `#25750`_ + - **PR** `#25750`_: (*alekti*) Add file as supported protocol for file source_hash. Fixes `#25701`_. + | refs: `#26020`_ + +- **PR** `#26016`_: (*basepi*) Revert "Deep merge of pillar lists" + @ *2015-08-05T04:59:52Z* + + - **ISSUE** `#22241`_: (*masterkorp*) Salt master not properly generating the map + | refs: `#25358`_ + - **PR** `#25358`_: (*dkiser*) Deep merge of pillar lists + | refs: `#26016`_ + +- **PR** `#25992`_: (*twangboy*) Refactor win_system.py + @ *2015-08-05T04:54:18Z* + + - **ISSUE** `#12255`_: (*eliasp*) 'system.set_computer_desc' fails with non-ASCII chars + | refs: `#25992`_ + - **ISSUE** `#3`_: (*thatch45*) libvirt module + +- **PR** `#26002`_: (*twangboy*) Fixed regex to account for comment character followed by whitespace + @ *2015-08-04T22:28:11Z* + + - **ISSUE** `#25948`_: (*twangboy*) Fix uncomment function to handle spaces + | refs: `#26002`_ + +- **PR** `#25970`_: (*jfindlay*) accept addition of layman overlay + @ *2015-08-04T15:42:28Z* + + - **ISSUE** `#25949`_: (*godlike64*) layman.add does not work with unofficial overlays + | refs: `#25970`_ + +- **PR** `#25971`_: (*basepi*) [2015.5] salt.modules.reg Add spaces for strings split across multiple lines + @ *2015-08-04T15:39:48Z* + +- **PR** `#25990`_: (*rallytime*) Back-port `#25976`_ to 2015.5 + @ *2015-08-04T14:36:53Z* + + - **PR** `#25976`_: (*fleaflicker*) Typo in help output + | refs: `#25990`_ + +- **PR** `#25996`_: (*attiasr*) fix msiexec package remove + @ *2015-08-04T14:36:31Z* + +- **PR** `#25966`_: (*rallytime*) Back-port `#25864`_ to 2015.5 + @ *2015-08-03T18:48:26Z* + + - **ISSUE** `#25863`_: (*peterdemin*) pkg.installed fails on already installed package if it is in versionlock.list + | refs: `#25864`_ + - **PR** `#25864`_: (*peterdemin*) `#25863`_ state.pkg.installed fix + | refs: `#25966`_ + +- **PR** `#25967`_: (*rallytime*) Back-port `#25917`_ to 2015.5 + @ *2015-08-03T18:48:02Z* + + - **PR** `#25917`_: (*jmdcal*) adding missing format string + | refs: `#25967`_ + +- **PR** `#25895`_: (*basepi*) [2015.5] Merge forward from 2014.7 to 2015.5 + @ *2015-08-03T17:12:37Z* + + - **ISSUE** `#23764`_: (*es1o*) source_hash from local file is not supported. + | refs: `#25750`_ + - **PR** `#25750`_: (*alekti*) Add file as supported protocol for file source_hash. Fixes `#25701`_. + | refs: `#26020`_ + - **PR** `#25704`_: (*cachedout*) Ensure prior alignment with master_type in 2014.7 + - **PR** `#25657`_: (*MrCitron*) Add the ability to specify a base pattern for carbon returner + - **PR** `#25633`_: (*AkhterAli*) Update loader.py + +- **PR** `#25941`_: (*jfindlay*) add timelib to dependency versions + @ *2015-08-03T12:23:42Z* + + - **ISSUE** `#25850`_: (*ssgward*) Need to add packages to --versions-report + | refs: `#25941`_ + +- **PR** `#25951`_: (*garethgreenaway*) Log when event.fire and event.fire_master fail. + @ *2015-08-03T00:19:45Z* + +- **PR** `#25942`_: (*jfindlay*) typo in minion doc + @ *2015-07-31T23:34:55Z* + + - **ISSUE** `#25838`_: (*grep4linux*) docs disable_modules documentation typo + | refs: `#25942`_ + +- **PR** `#25938`_: (*jacobhammons*) Doc on using syndic with multimaster + @ *2015-07-31T23:05:05Z* + + - **PR** `#14690`_: (*jacksontj*) Multi syndic + | refs: `#25938`_ + +- **PR** `#25848`_: (*twangboy*) Added allusers="1" when installing msi + @ *2015-07-31T20:33:17Z* + + - **ISSUE** `#25839`_: (*twangboy*) ALLUSERS="1" should be a default when installing MSI's + | refs: `#25848`_ + +- **PR** `#25898`_: (*jfindlay*) clarify and expand syndic docs + @ *2015-07-31T20:01:23Z* + +- **PR** `#25927`_: (*jacksontj*) Pass actual renderers to the Reactor's Compiler + @ *2015-07-31T20:00:17Z* + + - **ISSUE** `#25852`_: (*UtahDave*) Salt loader is not loading Salt vars in reactor python renderer + | refs: `#25927`_ + +- **PR** `#25921`_: (*cachedout*) Handle non-ascii in state log + @ *2015-07-31T17:41:30Z* + + - **ISSUE** `#25810`_: (*nvx*) winpkg highstate fails when a new package name contains a unicide character + | refs: `#25921`_ + +- **PR** `#25919`_: (*TheBigBear*) Minor update to msi un-installer info + @ *2015-07-31T17:39:48Z* + +- **PR** `#25905`_: (*rallytime*) Back-port `#25982`_ to 2015.5 + @ *2015-07-30T23:24:19Z* + + - **PR** `#25892`_: (*TheBigBear*) Update 7-zip msi un-installer instructions + | refs: `#25905`_ + +- **PR** `#25890`_: (*rallytime*) Back-port `#25698`_ to 2015.5 + @ *2015-07-30T23:12:09Z* + + - **ISSUE** `#25577`_: (*yellow1912*) Wrong indentation in document + | refs: `#25696`_ + - **PR** `#25698`_: (*rallytime*) Back-port `#25659`_ to 2015.8 + | refs: `#25890`_ + - **PR** `#25696`_: (*AkhterAli*) Update schedule.py + - **PR** `#25659`_: (*isbm*) Bugfix: crash at getting non-existing repo + | refs: `#25698`_ + +- **PR** `#25894`_: (*jacobhammons*) Minor doc bug fixes + @ *2015-07-30T23:02:34Z* + + - **ISSUE** `#25650`_: (*jacksontj*) state.running documentation is incorrect + | refs: `#25894`_ + - **ISSUE** `#24042`_: (*whiteinge*) The state_events setting is not documented + | refs: `#25894`_ + - **ISSUE** `#23788`_: (*k5jj*) functions in drac.py module do not match documentation + | refs: `#25894`_ + - **ISSUE** `#21296`_: (*Lothiraldan*) Possible minion enumeration using saltutil.find_job and eauth + | refs: `#25894`_ + +- **PR** `#25877`_: (*rallytime*) Protect against passing a map file in addition to VM names with --destroy + @ *2015-07-30T21:55:45Z* + + - **ISSUE** `#24036`_: (*arthurlogilab*) [salt-cloud] Protect against passing command line arguments as names for the --destroy command in map files + | refs: `#25877`_ + +- **PR** `#25870`_: (*rallytime*) Back-port `#25824`_ to 2015.5 + @ *2015-07-30T21:54:35Z* + + - **PR** `#25824`_: (*klyr*) Fix get_managed() in file.py module for local files + | refs: `#25870`_ + +- **PR** `#25885`_: (*t0rrant*) Update Debian changelog + @ *2015-07-30T20:05:59Z* + +- **PR** `#25875`_: (*rallytime*) Back-port `#25862`_ to 2015.5 + @ *2015-07-30T17:34:02Z* + + - **ISSUE** `#25478`_: (*zyio*) salt-ssh - Unable to locate current thin version + | refs: `#25862`_ + - **ISSUE** `#25026`_: (*sylvia-wang*) salt-ssh "Failure deploying thin" when using salt module functions + | refs: `#25862`_ + - **PR** `#25862`_: (*zyio*) Adding SCP_NOT_FOUND exit code + | refs: `#25875`_ + +- **PR** `#25873`_: (*rallytime*) Back-port `#25855`_ to 2015.5 + @ *2015-07-30T17:33:55Z* + + - **PR** `#25855`_: (*puneetk*) Patch 3 + | refs: `#25873`_ + +- **PR** `#25871`_: (*rallytime*) Back-port `#25829`_ to 2015.5 + @ *2015-07-30T17:33:43Z* + + - **PR** `#25829`_: (*peterdemin*) Fixed typo in salt.states.saltmod.function doc string + | refs: `#25871`_ + +- **PR** `#25869`_: (*rallytime*) Back-port `#25788`_ to 2015.5 + @ *2015-07-30T17:33:33Z* + + - **ISSUE** `#24002`_: (*csakoda*) File lock contention on windows minions causing highstate crash + | refs: `#25788`_ + - **PR** `#25788`_: (*opdude*) Catch a hard crash when running highstate on windows + | refs: `#25869`_ + +- **PR** `#25853`_: (*davidjb*) Make ssh-id-wrapper accessible to non-root users + @ *2015-07-30T16:49:47Z* + + - **ISSUE** `#19532`_: (*stolendog*) salt-ssh running git clone with not root user + | refs: `#25853`_ + +- **PR** `#25856`_: (*jfindlay*) expand minion reauth scalability documentation + @ *2015-07-30T15:33:17Z* + + - **ISSUE** `#25447`_: (*spo0nman*) SaltMaster is crippled with Minion Re-Authentication + | refs: `#25856`_ + +- **PR** `#25840`_: (*jfindlay*) add note to winrepo state docs about required grain + @ *2015-07-30T14:38:27Z* + + - **ISSUE** `#25801`_: (*themalkolm*) Update docs that salt.states.winrepo requires `roles:salt-master` in grains. + | refs: `#25840`_ + +- **PR** `#25846`_: (*jfindlay*) rework deprecation documentation for release names + @ *2015-07-30T13:26:21Z* + + - **ISSUE** `#25827`_: (*0xf10e*) "Deprecating Code" doesn't mention Usage of warn_until() w/ Release Names + | refs: `#25846`_ + +- **PR** `#25833`_: (*jahamn*) Allows cp.push to recreate empty files + @ *2015-07-29T16:14:48Z* + + - **ISSUE** `#23288`_: (*UtahDave*) cp.push fails to recreate empty files. + | refs: `#25833`_ + +- **PR** `#25831`_: (*rallytime*) Add salt:// to key_url options to docs for pkgrepo.managed + @ *2015-07-29T15:38:43Z* + + - **ISSUE** `#11474`_: (*JensRantil*) pkgrepo.managed key_url: salt:// always use `base` env + | refs: `#25831`_ + +- **PR** `#25807`_: (*rallytime*) Provide helpful error when using actions with a mapfile + @ *2015-07-29T15:30:15Z* + + - **ISSUE** `#22699`_: (*arthurlogilab*) salt-cloud fails on KeyError when given a nonexistant action + | refs: `#25807`_ + +- **PR** `#25818`_: (*jfindlay*) fix autoruns list + @ *2015-07-29T15:29:20Z* + +- **PR** `#25826`_: (*anlutro*) Check that "onchanges" is a list + @ *2015-07-29T15:00:28Z* + +- **PR** `#25798`_: (*twangboy*) Fixed stacktrace on package name not found + @ *2015-07-28T22:40:14Z* + + - **ISSUE** `#25258`_: (*nickw8*) windows minion repo not updating + | refs: `#25798`_ + +- **PR** `#25797`_: (*twangboy*) Changed repocache back to cached_repo + @ *2015-07-28T22:39:32Z* + + - **ISSUE** `#25437`_: (*lorengordon*) Stacktrace on Windows when running pkg.list_pkgs + | refs: `#25598`_ `#25763`_ + - **PR** `#25763`_: (*twangboy*) Fix 25437 + | refs: `#25797`_ + +- **PR** `#25793`_: (*rallytime*) Back-port `#25730`_ to 2015.5 + @ *2015-07-28T19:37:34Z* + + - **PR** `#25730`_: (*sjorge*) patchelf lives in pkgsrc + | refs: `#25793`_ + +- **PR** `#25792`_: (*rallytime*) Back-port `#25688`_ to 2015.5 + @ *2015-07-28T19:37:17Z* + + - **PR** `#25688`_: (*bclermont*) Don't acquire lock if there is no formatter + | refs: `#25792`_ + +- **PR** `#25796`_: (*cachedout*) Remove debug from docs + @ *2015-07-28T17:35:59Z* + +- **PR** `#25749`_: (*jahamn*) Allow zpool.create on character devices + @ *2015-07-28T16:01:40Z* + + - **ISSUE** `#24920`_: (*voileux*) module.zpool.create on character device is not possible by salt + | refs: `#25749`_ + +- **PR** `#25685`_: (*twangboy*) Fixed regex issues with comment and uncomment + @ *2015-07-28T15:29:49Z* + +- **PR** `#25763`_: (*twangboy*) Fix 25437 + | refs: `#25797`_ + @ *2015-07-28T15:29:27Z* + + - **ISSUE** `#25437`_: (*lorengordon*) Stacktrace on Windows when running pkg.list_pkgs + | refs: `#25598`_ `#25763`_ + +- **PR** `#25752`_: (*thatch45*) State top saltenv + @ *2015-07-28T01:02:10Z* + +- **PR** `#25755`_: (*twangboy*) Fixed problem with dunder functions not being passed + @ *2015-07-27T19:31:22Z* + + - **ISSUE** `#25717`_: (*twangboy*) Problem with chocolatey module not loading + | refs: `#25755`_ + +- **PR** `#25648`_: (*twangboy*) Clarified functionality of reg module, fixed state to work with new module + @ *2015-07-27T19:30:33Z* + + - **ISSUE** `#25352`_: (*m03*) reg.absent reporting incorrect results + | refs: `#25648`_ + - **ISSUE** `#1`_: (*thatch45*) Enable regex on the salt cli + +- **PR** `#25740`_: (*rallytime*) Back-port `#25722`_ to 2015.5 + @ *2015-07-27T16:08:40Z* + + - **ISSUE** `#25154`_: (*uvsmtid*) All data mixed on STDOUT together should generate valid JSON output + | refs: `#25722`_ + - **ISSUE** `#25153`_: (*uvsmtid*) Multiple results should generate valid JSON output + | refs: `#25722`_ + - **PR** `#25722`_: (*uvsmtid*) Minor docs changes to emphasize JSON output problems without `--static` option + | refs: `#25740`_ + +- **PR** `#25739`_: (*rallytime*) Back-port `#25709`_ to 2015.5 + @ *2015-07-27T16:08:27Z* + + - **PR** `#25709`_: (*colekowalski*) add direct-io-mode to mount_invisible_options + | refs: `#25739`_ + - **PR** `#25699`_: (*rallytime*) Back-port `#25660`_ to 2015.5 + | refs: `#25709`_ + - **PR** `#25660`_: (*colekowalski*) add glusterfs' direct-io-mode to mount_invisible_keys + | refs: `#25699`_ `#25709`_ + +- **PR** `#25738`_: (*rallytime*) Back-port `#25671`_ to 2015.5 + @ *2015-07-27T16:08:23Z* + + - **PR** `#25671`_: (*niq000*) added a parameter so verifying SSL is now optional instead of hard-coded + | refs: `#25738`_ + +- **PR** `#25737`_: (*rallytime*) Back-port `#25608`_ to 2015.5 + @ *2015-07-27T16:08:18Z* + + - **ISSUE** `#25229`_: (*rall0r*) Module git.latest kills target directory when test=True + | refs: `#25608`_ + - **PR** `#25608`_: (*rall0r*) Fix: prevent git.latest from removing target + | refs: `#25737`_ + +- **PR** `#25733`_: (*davidjb*) Avoid IndexError when listing mounts if mount output ends in newline + @ *2015-07-27T16:08:05Z* + +- **PR** `#25705`_: (*blackduckx*) Support for setm augeas command. + @ *2015-07-27T16:07:10Z* + + - **ISSUE** `#22460`_: (*onmeac*) Command setm is not supported (yet) + | refs: `#25705`_ + +- **PR** `#25703`_: (*cachedout*) Return to `str` for master_type for 2015.5 + @ *2015-07-27T16:06:22Z* + +- **PR** `#25702`_: (*twangboy*) Fixed win_user module for groups with spaces in the name + @ *2015-07-27T15:06:33Z* + + - **ISSUE** `#25144`_: (*johnccfm*) user.present on Windows fails to add user to groups if group name contains a space + | refs: `#25702`_ + +- **PR** `#25711`_: (*twangboy*) Fixed problem with win_servermanager.list_installed + @ *2015-07-27T15:05:48Z* + + - **ISSUE** `#25351`_: (*m03*) win_servermanager.list_installed failing with "IndexError: list index out of range" + | refs: `#25711`_ + +- **PR** `#25714`_: (*cachedout*) Display warning when progressbar can't be loaded + @ *2015-07-25T00:10:13Z* + + - **ISSUE** `#25435`_: (*yee379*) progressbar dependency missing + | refs: `#25714`_ + +- **PR** `#25699`_: (*rallytime*) Back-port `#25660`_ to 2015.5 + | refs: `#25709`_ + @ *2015-07-24T22:11:40Z* + + - **PR** `#25660`_: (*colekowalski*) add glusterfs' direct-io-mode to mount_invisible_keys + | refs: `#25699`_ `#25709`_ + +- **PR** `#25694`_: (*s0undt3ch*) Salt-SSH fix for `#25689`_ + @ *2015-07-24T21:41:57Z* + + - **ISSUE** `#25689`_: (*anlutro*) Minion log in salt-ssh + | refs: `#25694`_ + +- **PR** `#25710`_: (*jahamn*) Integration Testcase for Issue 25250 + @ *2015-07-24T20:57:33Z* + + - **ISSUE** `#25250`_: (*wipfs*) 'force' option in copy state deletes target file + | refs: `#25461`_ `#25710`_ + +- **PR** `#25680`_: (*basepi*) [2015.5] Move cmd.run jinja aliasing to a wrapper class to prevent side effects + @ *2015-07-24T19:52:10Z* + + - **PR** `#25049`_: (*terminalmage*) Fix cmd.run when cross-called in a state/execution module + | refs: `#25680`_ + +- **PR** `#25682`_: (*basepi*) [2015.5] Fix parsing args with just a hash (#) + @ *2015-07-24T19:52:01Z* + +- **PR** `#25695`_: (*stanislavb*) Configurable AWS region & region from IAM metadata + @ *2015-07-24T19:36:40Z* + +- **PR** `#25645`_: (*kev009*) Fix pkgng provider to work with a sources list and the underlying pkg… + @ *2015-07-24T16:33:18Z* + +- **PR** `#25677`_: (*aneeshusa*) Fix pacman.list_upgrades when refresh=True. + @ *2015-07-24T16:30:06Z* + +- **PR** `#25675`_: (*UtahDave*) Use OS line endings with contents on file.managed + @ *2015-07-24T16:29:50Z* + + - **ISSUE** `#25674`_: (*UtahDave*) file.managed with contents parameter uses wrong line endings on Windows + | refs: `#25675`_ + +- **PR** `#25676`_: (*basepi*) Update release candidate docs to 2015.8.0rc2 + @ *2015-07-23T20:29:37Z* + +- **PR** `#25666`_: (*nmadhok*) Check if the properties exist before looping over them causing KeyError + @ *2015-07-23T17:55:40Z* + + - **ISSUE** `#25665`_: (*nmadhok*) salt-cloud VMware driver fails with KeyErrors if there's any existing machine in the VMware infrastructure in (invalid state) + | refs: `#25666`_ + +- **PR** `#25656`_: (*anlutro*) Fix locale detection in debian/gentoo + @ *2015-07-23T16:46:40Z* + +- **PR** `#25661`_: (*rallytime*) Back-port `#25624`_ to 2015.5 + @ *2015-07-23T16:26:48Z* + + - **PR** `#25624`_: (*bobrik*) Fix typo in get_routes example for debian_ip + | refs: `#25661`_ + +- **PR** `#25662`_: (*rallytime*) Back-port `#25638`_ to 2015.5 + @ *2015-07-23T16:26:40Z* + + - **ISSUE** `#15209`_: (*hubez*) file.manage: source_hash not working with s3:// (2014.7.0rc1) + | refs: `#25638`_ + - **PR** `#25638`_: (*TronPaul*) fix bad merge in 99fc7ec + | refs: `#25662`_ + +- **PR** `#25644`_: (*cachedout*) pillar doc fix + @ *2015-07-22T22:57:23Z* + + - **ISSUE** `#25413`_: (*zizkebab*) pillar_opts default behavior is not reflected in the docs + | refs: `#25644`_ + +- **PR** `#25642`_: (*cachedout*) Warn on pillar schedule delete + @ *2015-07-22T22:04:12Z* + + - **ISSUE** `#25540`_: (*dennisjac*) salt highstate schedule cannot be removed + | refs: `#25642`_ + +- **PR** `#25598`_: (*twangboy*) Fixed problem trying to load file with name of boolean type + @ *2015-07-22T17:07:49Z* + + - **ISSUE** `#25437`_: (*lorengordon*) Stacktrace on Windows when running pkg.list_pkgs + | refs: `#25598`_ `#25763`_ + * 7b79e433 Merge pull request `#25598`_ from twangboy/fix_25437 + +- **PR** `#25604`_: (*terminalmage*) Move patching of mock_open to within test + @ *2015-07-22T16:53:55Z* + + - **ISSUE** `#25323`_: (*terminalmage*) unit.modules.tls_test fails with older mock + | refs: `#25604`_ + +- **PR** `#25609`_: (*s0undt3ch*) [2015.5] Update the bootstrap script to latest release v2015.07.22 + @ *2015-07-22T16:28:52Z* + + - **ISSUE** `#630`_: (*syphernl*) Allow for an include statement in config files + | refs: `#25609`_ + - **PR** `#627`_: (*chjohnst*) add saltversion grain + | refs: `#25609`_ + +- **PR** `#25603`_: (*terminalmage*) Add version_cmp function to yumpkg.py + @ *2015-07-22T15:42:29Z* + + - **ISSUE** `#21912`_: (*rvora*) pkg.latest not updating the package on CentOS though yum reports an update available + | refs: `#25603`_ + +- **PR** `#25590`_: (*garethgreenaway*) 2015.5 scheduled jobs return data + @ *2015-07-21T21:57:42Z* + + - **ISSUE** `#25560`_: (*dennisjac*) scheduled highstate runs don't return results to the job cache + | refs: `#25590`_ + +- **PR** `#25584`_: (*rallytime*) Back-port `#24054`_ and `#25576`_ to 2015.5 + @ *2015-07-21T21:16:38Z* + + - **PR** `#25576`_: (*pcn*) s3fs breaks when fetching files from s3 + | refs: `#25584`_ + - **PR** `#24054`_: (*mgwilliams*) s3.head: return useful data + | refs: `#25584`_ + +- **PR** `#25589`_: (*jahamn*) Fixes ssh_known_host not taking port into account + @ *2015-07-21T21:15:06Z* + + - **ISSUE** `#23626`_: (*mirko*) salt state 'ssh_known_hosts' doesn't take 'port' into account + | refs: `#25589`_ + +- **PR** `#25573`_: (*EvaSDK*) Do not execute bootstrap script twice + @ *2015-07-21T18:20:04Z* + + - **PR** `#25465`_: (*EvaSDK*) 2015.5.3 LXC module fixes + | refs: `#25573`_ + +- **PR** `#25580`_: (*attiasr*) use explicit utf-8 decoding (`#25532`_) + @ *2015-07-21T15:40:49Z* + + - **ISSUE** `#25532`_: (*attiasr*) salt/modules/win_pkg.py list_pkgs is broken (encoding issues) + | refs: `#25556`_ `#25580`_ + +- **PR** `#25568`_: (*twangboy*) Fixed win_useradd module to add fullname + @ *2015-07-21T14:30:25Z* + + - **ISSUE** `#25206`_: (*jfindlay*) fullname issues with user.add state on windows + | refs: `#25568`_ + +- **PR** `#25561`_: (*twangboy*) Fixed the gem module to work on windows... without injection + @ *2015-07-20T21:12:15Z* + + - **ISSUE** `#21041`_: (*deuscapturus*) state module gem.installed not working on Windows. + | refs: `#25430`_ `#25561`_ `#25428`_ + - **PR** `#25428`_: (*twangboy*) Fixed the gem module to work on windows + | refs: `#25561`_ + +- **PR** `#25521`_: (*cachedout*) Fix outputter for state.orch + @ *2015-07-20T19:30:14Z* + +- **PR** `#25563`_: (*basepi*) [2015.5] Merge forward from 2014.7 to 2015.5 + @ *2015-07-20T19:27:36Z* + + - **PR** `#25416`_: (*cachedout*) Fix broken keyword + +- **PR** `#25559`_: (*cachedout*) Lint win_pkg + @ *2015-07-20T17:46:29Z* + +- **PR** `#25556`_: (*attiasr*) fix for `#25532`_ + @ *2015-07-20T17:45:11Z* + + - **ISSUE** `#25532`_: (*attiasr*) salt/modules/win_pkg.py list_pkgs is broken (encoding issues) + | refs: `#25556`_ `#25580`_ + +- **PR** `#25554`_: (*jfindlay*) verify_ssl=True for s3 ext pillar + @ *2015-07-20T17:43:38Z* + + - **ISSUE** `#25538`_: (*stanislavb*) S3 ext_pillar configuration requires verify_ssl + | refs: `#25554`_ + +- **PR** `#25551`_: (*rallytime*) Backport `#25530`_ to 2015.5 + @ *2015-07-20T17:43:00Z* + + - **PR** `#25530`_: (*andre-luiz-dos-santos*) The variable name must be last + | refs: `#25551`_ + +- **PR** `#25533`_: (*attiasr*) port 445 for windows bootstraping + @ *2015-07-20T15:13:06Z* + +- **PR** `#25525`_: (*gtmanfred*) add make _prepare an alias for postinitio + @ *2015-07-20T15:12:38Z* + + - **ISSUE** `#25432`_: (*gtmanfred*) [2015.5.3][raet] raet error with SaltRaetRoadStackJoiner + | refs: `#25525`_ + +- **PR** `#25519`_: (*rallytime*) Backport vmware driver to 2015.5 branch + @ *2015-07-20T15:11:26Z* + + - **ISSUE** `#25511`_: (*rallytime*) Make provider --> driver change backward compatible + | refs: `#25519`_ `#25519`_ + - **ISSUE** `#23574`_: (*CedNantes*) Failed to Deploy Salt-Minion on a Win 2012 R2 using wmware Cloud Driver from Develop branch + | refs: `#25519`_ + +- **PR** `#25542`_: (*Oro*) Fix hipchat.send_message when using API v2 + @ *2015-07-20T15:09:13Z* + +- **PR** `#25531`_: (*rallytime*) Back-port `#25529`_ to 2015.5 + @ *2015-07-18T19:16:10Z* + + - **PR** `#25529`_: (*davidjb*) Fix minor typo in best practice example + | refs: `#25531`_ + +- **PR** `#25528`_: (*davidjb*) Fix typo in extend declaration doco + @ *2015-07-18T14:22:06Z* + +- **PR** `#25517`_: (*rallytime*) Back-port `#25486`_ to 2015.5 + @ *2015-07-17T21:49:26Z* + + - **ISSUE** `#25486`_: (*whiteinge*) Highstate outputter not used for state.apply + | refs: `#25517`_ + - **PR** `#25485`_: (*attiasr*) fix file downloads on windows + +- **PR** `#25516`_: (*rallytime*) Back-port `#25483`_ to 2015.5 + @ *2015-07-17T21:49:05Z* + + - **ISSUE** `#25479`_: (*alexandrsushko*) multiple mount.mounted of one device + | refs: `#25483`_ + - **PR** `#25483`_: (*alexandrsushko*) Added 'none' to the set of specialFSes + | refs: `#25516`_ + +- **PR** `#25513`_: (*garethgreenaway*) fixes to schedule.add documentation in 2015.5 + @ *2015-07-17T17:03:24Z* + + - **ISSUE** `#25493`_: (*blackduckx*) Issue with job_args on schedule.add command + | refs: `#25513`_ + +- **PR** `#25465`_: (*EvaSDK*) 2015.5.3 LXC module fixes + | refs: `#25573`_ + @ *2015-07-17T15:57:54Z* + +- **PR** `#25506`_: (*s0undt3ch*) [2015.5] Update bootstrap script to latest stable release, v2015.07.17 + @ *2015-07-17T15:40:38Z* + + - **ISSUE** `#25456`_: (*julienlavergne*) [2015.8.0rc1] salt-bootstrap fails to install salt master + | refs: `#25506`_ + - **ISSUE** `#25270`_: (*iggy*) [2015.8.0rc1] salt-bootstrap fails to properly install a minion + | refs: `#25506`_ + - **ISSUE** `#625`_: (*whiteinge*) `cmd.run` state `user` flag is not working + | refs: `#25506`_ `#632`_ + - **ISSUE** `#611`_: (*fatbox*) Peer interface fails to return data occasionally + | refs: `#25506`_ + - **ISSUE** `#607`_: (*thatch45*) next level -X support + | refs: `#25506`_ + - **ISSUE** `#598`_: (*syphernl*) Explanation on how to execute interactive installs + | refs: `#25506`_ + - **ISSUE** `#455`_: (*whiteinge*) Document common troubleshooting tips + | refs: `#25506`_ + - **PR** `#624`_: (*chjohnst*) Docs are not correct with network.ping as args are not supported + | refs: `#25506`_ + - **PR** `#621`_: (*akoumjian*) Adding ec2 cloud-init bootstrap docs + | refs: `#25506`_ + - **PR** `#606`_: (*terminalmage*) need empty line before code blocks. added ones that were missing. + | refs: `#25506`_ + - **PR** `#602`_: (*terminalmage*) State-related documentation changes + | refs: `#25506`_ + +- **PR** `#25498`_: (*jfindlay*) only read /proc/1/cmdline if it exists + @ *2015-07-17T15:35:33Z* + + - **ISSUE** `#25454`_: (*mschiff*) Regression: salt 2015.5 not working in secure chroot anymore. + | refs: `#25498`_ + +- **PR** `#25487`_: (*rallytime*) Back-port `#25464`_ to 2015.5 + @ *2015-07-16T16:58:36Z* + + - **PR** `#25464`_: (*jquast*) docfix: "cache_jobs: False" => grains_cache: False" + | refs: `#25487`_ + +- **PR** `#25482`_: (*oeuftete*) Fix docker.running detection of running container + @ *2015-07-16T16:58:29Z* + + - **PR** `#2015`_: (*thekuffs*) Esky / bbfreeze support + +- **PR** `#25468`_: (*joejulian*) Add support for pyOpenSSL > 0.10 + @ *2015-07-16T15:10:30Z* + + - **ISSUE** `#25384`_: (*rickh563*) pyopenssl 0.14 requirement in 2015.5.3 does not work in RHEL6 : ZD-364 + | refs: `#25468`_ + +- **PR** `#25467`_: (*rallytime*) Add lxml dependency to opennebula docs + @ *2015-07-16T15:09:57Z* + +- **PR** `#25461`_: (*jahamn*) Update file, if force option and content not same + @ *2015-07-15T20:15:07Z* + + - **ISSUE** `#25250`_: (*wipfs*) 'force' option in copy state deletes target file + | refs: `#25461`_ `#25710`_ + - **ISSUE** `#24647`_: (*nmadhok*) salt.states.file.copy does not copy the file if it already exists with force=True + | refs: `#25461`_ + +- **PR** `#25438`_: (*rallytime*) Reduce digital_ocean_v2 API call frequency + @ *2015-07-15T19:40:18Z* + + - **ISSUE** `#25431`_: (*namcois*) Digital Ocean v2 reducing API calls by adding per_page + | refs: `#25438`_ + +- **PR** `#25457`_: (*jacksontj*) Saltnado + @ *2015-07-15T17:50:12Z* + + - **PR** `#25427`_: (*tony-cocco*) Saltnado runner client results in blocking call despite being set-up as Runner.async + | refs: `#25457`_ + +- **PR** `#25459`_: (*jahamn*) Fixed 'defulats' typo in verify.py + @ *2015-07-15T16:53:06Z* + +- **PR** `#25426`_: (*jquast*) bugfix: trailing "...done" in rabbitmq output (backport from 'develop' to 2015.5) + @ *2015-07-15T14:48:05Z* + +- **PR** `#25433`_: (*jleroy*) Support for IPv6 addresses scopes in network.interfaces (ifconfig) + @ *2015-07-15T14:44:09Z* + + - **PR** `#25151`_: (*jleroy*) Support for IPv6 addresses scopes in network.interfaces + | refs: `#25274`_ `#25433`_ + +- **PR** `#25430`_: (*twangboy*) Disabled rbenv execution module for Windows + @ *2015-07-15T14:41:18Z* + + - **ISSUE** `#21041`_: (*deuscapturus*) state module gem.installed not working on Windows. + | refs: `#25430`_ `#25561`_ `#25428`_ + +* c4b1584 Additional test case for question raised in `#1846`_ + + - **ISSUE** `#1846`_: (*seanchannel*) development dependencies + +- **PR** `#25420`_: (*techhat*) Move S3 to use AWS Signature Version 4 + @ *2015-07-14T22:03:09Z* + +- **PR** `#25418`_: (*twangboy*) Fixed problem with file.managed test=True + @ *2015-07-14T21:26:59Z* + + - **ISSUE** `#20441`_: (*deuscapturus*) State module file.managed returns an error on Windows and test=Test + | refs: `#25418`_ + +- **PR** `#25417`_: (*ahus1*) extended documentation about dependencies for dig module + @ *2015-07-14T20:49:51Z* + +- **PR** `#25411`_: (*basepi*) [2015.5] Merge forward from 2014.7 to 2015.5 + @ *2015-07-14T17:55:26Z* + + - **PR** `#25375`_: (*cachedout*) Fix error in config.py for master_type + - **PR** `#25324`_: (*jacobhammons*) Latest help theme updates + +- **PR** `#25406`_: (*anlutro*) Force arguments to aptpkg.version_cmp into strings + @ *2015-07-14T16:15:41Z* + +- **PR** `#25408`_: (*rallytime*) Back-port `#25399`_ to 2015.5 + @ *2015-07-14T16:09:06Z* + + - **PR** `#25399`_: (*jarpy*) Demonstrate per-minion client_acl. + | refs: `#25408`_ + +- **PR** `#25240`_: (*tankywoo*) file make os.walk only be called one + @ *2015-07-14T16:04:49Z* + +- **PR** `#25395`_: (*rallytime*) Back-port `#25389`_ to 2015.5 + @ *2015-07-14T03:26:34Z* + + - **PR** `#25389`_: (*l2ol33rt*) Adding entropy note for gpg renderer + | refs: `#25395`_ + +- **PR** `#25392`_: (*rallytime*) Back-port `#25256`_ to 2015.5 + @ *2015-07-14T03:25:13Z* + + - **PR** `#25256`_: (*yanatan16*) Dont assume source_hash exists + | refs: `#25392`_ + +- **PR** `#25398`_: (*twangboy*) Fix date + @ *2015-07-14T03:21:17Z* + +- **PR** `#25397`_: (*GideonRed*) Introduce standard error output when cli exits with non-zero status + @ *2015-07-14T03:20:24Z* + +- **PR** `#25386`_: (*cachedout*) Lint `#25383`_ + @ *2015-07-13T21:01:10Z* + + - **ISSUE** `#24444`_: (*michaelkrupp*) file.managed does not handle dead symlinks + | refs: `#25383`_ + - **PR** `#25383`_: (*jahamn*) Fix manage_file function in salt/modules/file.py to handle broken sym… + +- **PR** `#25383`_: (*jahamn*) Fix manage_file function in salt/modules/file.py to handle broken sym… + @ *2015-07-13T20:58:23Z* + + - **ISSUE** `#24444`_: (*michaelkrupp*) file.managed does not handle dead symlinks + | refs: `#25383`_ + +- **PR** `#25369`_: (*anlutro*) Fix aptpkg.version_cmp + @ *2015-07-13T20:18:45Z* + +- **PR** `#25379`_: (*jfindlay*) check for cwd before getting it + @ *2015-07-13T19:50:27Z* + + - **ISSUE** `#25337`_: (*eliasp*) `salt-call` from non-existend cwd backtraces + | refs: `#25379`_ + +- **PR** `#25334`_: (*jfindlay*) return all cmd info back to zypper fcn + @ *2015-07-13T17:03:29Z* + + - **ISSUE** `#25320`_: (*podloucky-init*) zypper module list_upgrades broken (2015.5.2) + | refs: `#25334`_ + +- **PR** `#25339`_: (*jfindlay*) update orchestration docs + @ *2015-07-13T16:04:26Z* + +- **PR** `#25358`_: (*dkiser*) Deep merge of pillar lists + | refs: `#26016`_ + @ *2015-07-13T15:51:01Z* + + - **ISSUE** `#22241`_: (*masterkorp*) Salt master not properly generating the map + | refs: `#25358`_ + +- **PR** `#25346`_: (*bechtoldt*) set correct indention in states/requisites.rst (docs), fixes `#25281`_ + @ *2015-07-13T15:34:45Z* + + - **ISSUE** `#25281`_: (*shinshenjs*) Unless usage in Official Doc syntax error? + +- **PR** `#25336`_: (*terminalmage*) Don't try to read init binary if it wasn't found + @ *2015-07-13T09:45:30Z* + +- **PR** `#25350`_: (*davidjb*) Fix documentation for file.blockreplace + @ *2015-07-13T03:41:20Z* + +- **PR** `#25326`_: (*rallytime*) Back-port `#20972`_ to 2015.5 + @ *2015-07-10T18:49:44Z* + + - **ISSUE** `#19288`_: (*oba11*) AssociatePublicIpAddress doesnt work with salt-cloud 2014.7.0 + | refs: `#20972`_ `#25326`_ + - **PR** `#20972`_: (*JohannesEbke*) Fix interface cleanup when using AssociatePublicIpAddress in `#19288`_ + | refs: `#25326`_ + +- **PR** `#25327`_: (*rallytime*) Back-port `#25290`_ to 2015.5 + @ *2015-07-10T18:49:37Z* + + - **ISSUE** `#24433`_: (*chrimi*) Salt locale state fails, if locale has not been generated + | refs: `#25290`_ + - **PR** `#25290`_: (*pcdummy*) Simple fix for locale.present on Ubuntu. + | refs: `#25327`_ + +- **PR** `#25328`_: (*rallytime*) Back-port `#25309`_ to 2015.5 + @ *2015-07-10T17:22:59Z* + + - **ISSUE** `#24827`_: (*yermulnik*) locale.present doesn't generate locales + | refs: `#25309`_ + - **PR** `#25309`_: (*davidjb*) Format /etc/locale.gen correctly in salt.modules.localemod.gen_locale + | refs: `#25328`_ + +- **PR** `#25322`_: (*jacobhammons*) version change to 2015.5.3 + @ *2015-07-10T16:11:24Z* + +- **PR** `#25308`_: (*jacksontj*) Make clear commands trace level logging + @ *2015-07-10T14:20:06Z* + + - **PR** `#24737`_: (*jacksontj*) Move AES command logging to trace + | refs: `#25308`_ + +- **PR** `#25269`_: (*jfindlay*) Extract tomcat war version + @ *2015-07-10T01:28:21Z* + + - **ISSUE** `#24520`_: (*nvx*) Tomcat module fails to extract version number from snapshot builds (2015.5 regression) + | refs: `#24927`_ + - **PR** `#24927`_: (*egarbi*) Tomcat module fails to extract version number from snapshot builds `#2`_… + | refs: `#25269`_ + +- **PR** `#25238`_: (*DmitryKuzmenko*) Pillarenv backport 2015.5 + @ *2015-07-10T01:25:07Z* + + - **ISSUE** `#18808`_: (*amendlik*) Add command line argument to select pillar environment + | refs: `#25238`_ + - **PR** `#23719`_: (*DmitryKuzmenko*) Support pillarenv cmdline in state.sls + +- **PR** `#25299`_: (*twangboy*) Added -NonInteractive so powershell doesn't hang waiting for input + @ *2015-07-09T21:00:16Z* + + - **ISSUE** `#13943`_: (*Supermathie*) Powershell commands that expect input hang forever + | refs: `#25299`_ + +- **PR** `#25301`_: (*jacobhammons*) bug fix for module function display in help + @ *2015-07-09T20:46:34Z* + +- **PR** `#25279`_: (*jacobhammons*) Additional docs on external and master job cache, assorted doc fixes + @ *2015-07-09T16:46:26Z* + + - **ISSUE** `#25277`_: (*jacobhammons*) CherryPy recommended versions + | refs: `#25279`_ + +- **PR** `#25274`_: (*jleroy*) Fix for issue `#25268`_ + @ *2015-07-09T13:36:26Z* + + - **ISSUE** `#25268`_: (*lichtamberg*) Salt not working anymore in 2015.8/develop: ValueError: 'scope' is not in list + | refs: `#25274`_ + - **PR** `#25151`_: (*jleroy*) Support for IPv6 addresses scopes in network.interfaces + | refs: `#25274`_ `#25433`_ + +- **PR** `#25272`_: (*twangboy*) Fixed problem with service not starting + @ *2015-07-08T23:29:48Z* + +- **PR** `#25225`_: (*nmadhok*) Backporting fix for issue `#25223`_ on 2015.5 branch + @ *2015-07-08T15:16:18Z* + + - **ISSUE** `#25223`_: (*nmadhok*) Runner occasionally fails with a RuntimeError when fired by a reactor + | refs: `#25225`_ + +- **PR** `#25214`_: (*rallytime*) A couple of doc fixes for the http tutorial + @ *2015-07-07T22:23:07Z* + +- **PR** `#25194`_: (*rallytime*) Update moto version check in boto_vpc_test and update min version + @ *2015-07-07T18:27:32Z* + + - **ISSUE** `#24272`_: (*rallytime*) Fix boto_vpc_test moto version check + | refs: `#25194`_ + +- **PR** `#25205`_: (*basepi*) Update releasecandidate docs + @ *2015-07-07T15:25:24Z* + +- **PR** `#25187`_: (*UtahDave*) Doc fixes: Fix misspelling and remove extraneous double spaces + @ *2015-07-07T01:07:04Z* + +- **PR** `#25182`_: (*cachedout*) Try to re-pack long floats as strs + @ *2015-07-07T01:06:43Z* + +- **PR** `#25185`_: (*rallytime*) Back-port `#25128`_ to 2015.5 + @ *2015-07-07T00:58:00Z* + + - **ISSUE** `#23822`_: (*sidcarter*) Zip file extracted permissions are incorrect + | refs: `#25128`_ + - **PR** `#25128`_: (*stanislavb*) Use cmd_unzip to preserve permissions + | refs: `#25185`_ + +- **PR** `#25181`_: (*rallytime*) Back-port `#25102`_ to 2015.5 + @ *2015-07-07T00:57:13Z* + + - **PR** `#25102`_: (*derBroBro*) Update win_network.py + | refs: `#25181`_ + +- **PR** `#25179`_: (*rallytime*) Back-port `#25059`_ to 2015.5 + @ *2015-07-07T00:56:44Z* + + - **ISSUE** `#24301`_: (*iggy*) influxdb_user and influxdb_database states need virtual functions + | refs: `#25059`_ + - **PR** `#25059`_: (*babilen*) Add virtual functions to influxdb state modules + | refs: `#25179`_ + +- **PR** `#25196`_: (*twangboy*) Fixed `#18919`_ false-positive on pkg.refresh + @ *2015-07-07T00:24:13Z* + + - **ISSUE** `#18919`_: (*giner*) Windows: pkg.refresh_db returns false-positive success + | refs: `#25196`_ + +- **PR** `#25180`_: (*rallytime*) Back-port `#25088`_ to 2015.5 + @ *2015-07-06T20:33:45Z* + + - **PR** `#25088`_: (*supertom*) Update + | refs: `#25180`_ + +- **PR** `#25191`_: (*basepi*) Add extrndest back to fileclient.is_cached in 2015.5 + @ *2015-07-06T19:35:24Z* + + - **PR** `#25117`_: (*basepi*) Fix fileclient.is_cached + | refs: `#25191`_ + +- **PR** `#25175`_: (*rallytime*) Back-port `#25020`_ to 2015.5 + @ *2015-07-06T18:53:19Z* + + - **ISSUE** `#25016`_: (*martinhoefling*) salt-run doc.execution fails with AttributeError + - **PR** `#25020`_: (*martinhoefling*) Fix for issue `#25016`_ + | refs: `#25175`_ + +- **PR** `#25173`_: (*rallytime*) Partial back-port of `#25019`_ + @ *2015-07-06T18:52:59Z* + + - **ISSUE** `#21879`_: (*bechtoldt*) Reference pages in documentation are outdated again + | refs: `#25019`_ + - **ISSUE** `#19262`_: (*bechtoldt*) salt.pillar.file_tree doesn't appear in the documentation + | refs: `#25019`_ + - **PR** `#25019`_: (*bechtoldt*) add missing module documentation to references + | refs: `#25173`_ + - **PR** `#24421`_: (*bechtoldt*) add missing module documentation + | refs: `#25019`_ + - **PR** `#21880`_: (*bechtoldt*) update references, fixes `#21879`_ + | refs: `#25019`_ + - **PR** `#20039`_: (*bechtoldt*) completing some doc references + | refs: `#25019`_ + +- **PR** `#25171`_: (*rallytime*) Back-port `#25001`_ to 2015.5 + @ *2015-07-06T18:51:53Z* + + - **PR** `#25001`_: (*jasonkeene*) Add docs for key arg in ssh_known_hosts.present + | refs: `#25171`_ + +- **PR** `#25170`_: (*rallytime*) Back-port `#24982`_ to 2015.5 + @ *2015-07-06T16:34:43Z* + + - **PR** `#24982`_: (*asyncsrc*) ec2 network_interfaces fix + | refs: `#25170`_ + +- **PR** `#25161`_: (*aneeshusa*) Allow checking for non-normalized systemd units. + @ *2015-07-06T15:15:31Z* + +- **PR** `#25151`_: (*jleroy*) Support for IPv6 addresses scopes in network.interfaces + | refs: `#25274`_ `#25433`_ + @ *2015-07-06T14:43:03Z* + +- **PR** `#25166`_: (*cachedout*) Lint `#25149`_ + @ *2015-07-06T14:40:29Z* + + - **ISSUE** `#24979`_: (*mavenAtHouzz*) [Discussion] Support for more than 1 netapi.rest_tornado server process + | refs: `#25149`_ + - **PR** `#25149`_: (*jacksontj*) Saltnado multiprocess support + | refs: `#25166`_ + +- **PR** `#25149`_: (*jacksontj*) Saltnado multiprocess support + | refs: `#25166`_ + @ *2015-07-06T14:38:43Z* + + - **ISSUE** `#24979`_: (*mavenAtHouzz*) [Discussion] Support for more than 1 netapi.rest_tornado server process + | refs: `#25149`_ + +- **PR** `#25120`_: (*d--j*) add missing continue for exeption case + @ *2015-07-02T19:38:45Z* + +- **PR** `#25117`_: (*basepi*) Fix fileclient.is_cached + | refs: `#25191`_ + @ *2015-07-02T19:38:26Z* + +- **PR** `#25087`_: (*0xf10e*) Fix execution module for glance - now based on 2015.5! + @ *2015-07-02T19:36:27Z* + +- **PR** `#25129`_: (*basepi*) [2015.5] Merge forward from 2014.7 to 2015.5 + @ *2015-07-02T17:37:40Z* + + - **ISSUE** `#18447`_: (*ryan-lane*) Can't install salt with raet using pip -e git + - **PR** `#25093`_: (*jaybocc2*) quick fix for issue `#18447`_ + - **PR** `#25069`_: (*puneetk*) Add a helper module function called list_enabled + +- **PR** `#25114`_: (*jfindlay*) Revert "Revert "adding states/postgres_database unit test case."" + @ *2015-07-02T01:01:29Z* + + - **PR** `#24798`_: (*jtand*) Revert "adding states/postgres_database unit test case." + | refs: `#25114`_ + - **PR** `#24329`_: (*jayeshka*) adding states/postgres_database unit test case. + | refs: `#24798`_ + +- **PR** `#24362`_: (*jayeshka*) adding states/postgres_user unit test case. + @ *2015-07-01T21:45:31Z* + +- **PR** `#24361`_: (*jayeshka*) adding states/postgres_schema unit test case. + @ *2015-07-01T21:44:56Z* + +- **PR** `#24331`_: (*jayeshka*) adding states/postgres_extension unit test case. + @ *2015-07-01T21:43:58Z* + +.. _`#1`: https://github.com/saltstack/salt/issues/1 +.. _`#11474`: https://github.com/saltstack/salt/issues/11474 +.. _`#12255`: https://github.com/saltstack/salt/issues/12255 +.. _`#13943`: https://github.com/saltstack/salt/issues/13943 +.. _`#14690`: https://github.com/saltstack/salt/pull/14690 +.. _`#15209`: https://github.com/saltstack/salt/issues/15209 +.. _`#18447`: https://github.com/saltstack/salt/issues/18447 +.. _`#1846`: https://github.com/saltstack/salt/issues/1846 +.. _`#18808`: https://github.com/saltstack/salt/issues/18808 +.. _`#18919`: https://github.com/saltstack/salt/issues/18919 +.. _`#19262`: https://github.com/saltstack/salt/issues/19262 +.. _`#19288`: https://github.com/saltstack/salt/issues/19288 +.. _`#19532`: https://github.com/saltstack/salt/issues/19532 +.. _`#2`: https://github.com/saltstack/salt/issues/2 +.. _`#20039`: https://github.com/saltstack/salt/pull/20039 +.. _`#2015`: https://github.com/saltstack/salt/pull/2015 +.. _`#20441`: https://github.com/saltstack/salt/issues/20441 +.. _`#20972`: https://github.com/saltstack/salt/pull/20972 +.. _`#21041`: https://github.com/saltstack/salt/issues/21041 +.. _`#21082`: https://github.com/saltstack/salt/issues/21082 +.. _`#21296`: https://github.com/saltstack/salt/issues/21296 +.. _`#21879`: https://github.com/saltstack/salt/issues/21879 +.. _`#21880`: https://github.com/saltstack/salt/pull/21880 +.. _`#21912`: https://github.com/saltstack/salt/issues/21912 +.. _`#22241`: https://github.com/saltstack/salt/issues/22241 +.. _`#22460`: https://github.com/saltstack/salt/issues/22460 +.. _`#22699`: https://github.com/saltstack/salt/issues/22699 +.. _`#23288`: https://github.com/saltstack/salt/issues/23288 +.. _`#23574`: https://github.com/saltstack/salt/issues/23574 +.. _`#23626`: https://github.com/saltstack/salt/issues/23626 +.. _`#23719`: https://github.com/saltstack/salt/pull/23719 +.. _`#23764`: https://github.com/saltstack/salt/issues/23764 +.. _`#23788`: https://github.com/saltstack/salt/issues/23788 +.. _`#23822`: https://github.com/saltstack/salt/issues/23822 +.. _`#24002`: https://github.com/saltstack/salt/issues/24002 +.. _`#24036`: https://github.com/saltstack/salt/issues/24036 +.. _`#24042`: https://github.com/saltstack/salt/issues/24042 +.. _`#24054`: https://github.com/saltstack/salt/pull/24054 +.. _`#24106`: https://github.com/saltstack/salt/issues/24106 +.. _`#24272`: https://github.com/saltstack/salt/issues/24272 +.. _`#24301`: https://github.com/saltstack/salt/issues/24301 +.. _`#24329`: https://github.com/saltstack/salt/pull/24329 +.. _`#24331`: https://github.com/saltstack/salt/pull/24331 +.. _`#24361`: https://github.com/saltstack/salt/pull/24361 +.. _`#24362`: https://github.com/saltstack/salt/pull/24362 +.. _`#24421`: https://github.com/saltstack/salt/pull/24421 +.. _`#24433`: https://github.com/saltstack/salt/issues/24433 +.. _`#24444`: https://github.com/saltstack/salt/issues/24444 +.. _`#24460`: https://github.com/saltstack/salt/issues/24460 +.. _`#24483`: https://github.com/saltstack/salt/issues/24483 +.. _`#24484`: https://github.com/saltstack/salt/issues/24484 +.. _`#24520`: https://github.com/saltstack/salt/issues/24520 +.. _`#24647`: https://github.com/saltstack/salt/issues/24647 +.. _`#24737`: https://github.com/saltstack/salt/pull/24737 +.. _`#24798`: https://github.com/saltstack/salt/pull/24798 +.. _`#24827`: https://github.com/saltstack/salt/issues/24827 +.. _`#24882`: https://github.com/saltstack/salt/issues/24882 +.. _`#24920`: https://github.com/saltstack/salt/issues/24920 +.. _`#24927`: https://github.com/saltstack/salt/pull/24927 +.. _`#24979`: https://github.com/saltstack/salt/issues/24979 +.. _`#24982`: https://github.com/saltstack/salt/pull/24982 +.. _`#25001`: https://github.com/saltstack/salt/pull/25001 +.. _`#25016`: https://github.com/saltstack/salt/issues/25016 +.. _`#25019`: https://github.com/saltstack/salt/pull/25019 +.. _`#25020`: https://github.com/saltstack/salt/pull/25020 +.. _`#25026`: https://github.com/saltstack/salt/issues/25026 +.. _`#25049`: https://github.com/saltstack/salt/pull/25049 +.. _`#25059`: https://github.com/saltstack/salt/pull/25059 +.. _`#25069`: https://github.com/saltstack/salt/pull/25069 +.. _`#25087`: https://github.com/saltstack/salt/pull/25087 +.. _`#25088`: https://github.com/saltstack/salt/pull/25088 +.. _`#25093`: https://github.com/saltstack/salt/pull/25093 +.. _`#25102`: https://github.com/saltstack/salt/pull/25102 +.. _`#25114`: https://github.com/saltstack/salt/pull/25114 +.. _`#25117`: https://github.com/saltstack/salt/pull/25117 +.. _`#25120`: https://github.com/saltstack/salt/pull/25120 +.. _`#25128`: https://github.com/saltstack/salt/pull/25128 +.. _`#25129`: https://github.com/saltstack/salt/pull/25129 +.. _`#25144`: https://github.com/saltstack/salt/issues/25144 +.. _`#25149`: https://github.com/saltstack/salt/pull/25149 +.. _`#25151`: https://github.com/saltstack/salt/pull/25151 +.. _`#25153`: https://github.com/saltstack/salt/issues/25153 +.. _`#25154`: https://github.com/saltstack/salt/issues/25154 +.. _`#25161`: https://github.com/saltstack/salt/pull/25161 +.. _`#25166`: https://github.com/saltstack/salt/pull/25166 +.. _`#25170`: https://github.com/saltstack/salt/pull/25170 +.. _`#25171`: https://github.com/saltstack/salt/pull/25171 +.. _`#25173`: https://github.com/saltstack/salt/pull/25173 +.. _`#25175`: https://github.com/saltstack/salt/pull/25175 +.. _`#25179`: https://github.com/saltstack/salt/pull/25179 +.. _`#25180`: https://github.com/saltstack/salt/pull/25180 +.. _`#25181`: https://github.com/saltstack/salt/pull/25181 +.. _`#25182`: https://github.com/saltstack/salt/pull/25182 +.. _`#25185`: https://github.com/saltstack/salt/pull/25185 +.. _`#25187`: https://github.com/saltstack/salt/pull/25187 +.. _`#25191`: https://github.com/saltstack/salt/pull/25191 +.. _`#25192`: https://github.com/saltstack/salt/issues/25192 +.. _`#25194`: https://github.com/saltstack/salt/pull/25194 +.. _`#25196`: https://github.com/saltstack/salt/pull/25196 +.. _`#25205`: https://github.com/saltstack/salt/pull/25205 +.. _`#25206`: https://github.com/saltstack/salt/issues/25206 +.. _`#25214`: https://github.com/saltstack/salt/pull/25214 +.. _`#25223`: https://github.com/saltstack/salt/issues/25223 +.. _`#25225`: https://github.com/saltstack/salt/pull/25225 +.. _`#25229`: https://github.com/saltstack/salt/issues/25229 +.. _`#25238`: https://github.com/saltstack/salt/pull/25238 +.. _`#25240`: https://github.com/saltstack/salt/pull/25240 +.. _`#25250`: https://github.com/saltstack/salt/issues/25250 +.. _`#25256`: https://github.com/saltstack/salt/pull/25256 +.. _`#25258`: https://github.com/saltstack/salt/issues/25258 +.. _`#25268`: https://github.com/saltstack/salt/issues/25268 +.. _`#25269`: https://github.com/saltstack/salt/pull/25269 +.. _`#25270`: https://github.com/saltstack/salt/issues/25270 +.. _`#25272`: https://github.com/saltstack/salt/pull/25272 +.. _`#25274`: https://github.com/saltstack/salt/pull/25274 +.. _`#25277`: https://github.com/saltstack/salt/issues/25277 +.. _`#25279`: https://github.com/saltstack/salt/pull/25279 +.. _`#25281`: https://github.com/saltstack/salt/issues/25281 +.. _`#25290`: https://github.com/saltstack/salt/pull/25290 +.. _`#25299`: https://github.com/saltstack/salt/pull/25299 +.. _`#25301`: https://github.com/saltstack/salt/pull/25301 +.. _`#25308`: https://github.com/saltstack/salt/pull/25308 +.. _`#25309`: https://github.com/saltstack/salt/pull/25309 +.. _`#25320`: https://github.com/saltstack/salt/issues/25320 +.. _`#25322`: https://github.com/saltstack/salt/pull/25322 +.. _`#25323`: https://github.com/saltstack/salt/issues/25323 +.. _`#25324`: https://github.com/saltstack/salt/pull/25324 +.. _`#25326`: https://github.com/saltstack/salt/pull/25326 +.. _`#25327`: https://github.com/saltstack/salt/pull/25327 +.. _`#25328`: https://github.com/saltstack/salt/pull/25328 +.. _`#25334`: https://github.com/saltstack/salt/pull/25334 +.. _`#25336`: https://github.com/saltstack/salt/pull/25336 +.. _`#25337`: https://github.com/saltstack/salt/issues/25337 +.. _`#25339`: https://github.com/saltstack/salt/pull/25339 +.. _`#25346`: https://github.com/saltstack/salt/pull/25346 +.. _`#25350`: https://github.com/saltstack/salt/pull/25350 +.. _`#25351`: https://github.com/saltstack/salt/issues/25351 +.. _`#25352`: https://github.com/saltstack/salt/issues/25352 +.. _`#25358`: https://github.com/saltstack/salt/pull/25358 +.. _`#25369`: https://github.com/saltstack/salt/pull/25369 +.. _`#25375`: https://github.com/saltstack/salt/pull/25375 +.. _`#25379`: https://github.com/saltstack/salt/pull/25379 +.. _`#25383`: https://github.com/saltstack/salt/pull/25383 +.. _`#25384`: https://github.com/saltstack/salt/issues/25384 +.. _`#25386`: https://github.com/saltstack/salt/pull/25386 +.. _`#25389`: https://github.com/saltstack/salt/pull/25389 +.. _`#25392`: https://github.com/saltstack/salt/pull/25392 +.. _`#25395`: https://github.com/saltstack/salt/pull/25395 +.. _`#25397`: https://github.com/saltstack/salt/pull/25397 +.. _`#25398`: https://github.com/saltstack/salt/pull/25398 +.. _`#25399`: https://github.com/saltstack/salt/pull/25399 +.. _`#25404`: https://github.com/saltstack/salt/pull/25404 +.. _`#25406`: https://github.com/saltstack/salt/pull/25406 +.. _`#25408`: https://github.com/saltstack/salt/pull/25408 +.. _`#25411`: https://github.com/saltstack/salt/pull/25411 +.. _`#25413`: https://github.com/saltstack/salt/issues/25413 +.. _`#25416`: https://github.com/saltstack/salt/pull/25416 +.. _`#25417`: https://github.com/saltstack/salt/pull/25417 +.. _`#25418`: https://github.com/saltstack/salt/pull/25418 +.. _`#25420`: https://github.com/saltstack/salt/pull/25420 +.. _`#25426`: https://github.com/saltstack/salt/pull/25426 +.. _`#25427`: https://github.com/saltstack/salt/pull/25427 +.. _`#25428`: https://github.com/saltstack/salt/pull/25428 +.. _`#25430`: https://github.com/saltstack/salt/pull/25430 +.. _`#25431`: https://github.com/saltstack/salt/issues/25431 +.. _`#25432`: https://github.com/saltstack/salt/issues/25432 +.. _`#25433`: https://github.com/saltstack/salt/pull/25433 +.. _`#25435`: https://github.com/saltstack/salt/issues/25435 +.. _`#25437`: https://github.com/saltstack/salt/issues/25437 +.. _`#25438`: https://github.com/saltstack/salt/pull/25438 +.. _`#25447`: https://github.com/saltstack/salt/issues/25447 +.. _`#25454`: https://github.com/saltstack/salt/issues/25454 +.. _`#25456`: https://github.com/saltstack/salt/issues/25456 +.. _`#25457`: https://github.com/saltstack/salt/pull/25457 +.. _`#25459`: https://github.com/saltstack/salt/pull/25459 +.. _`#25461`: https://github.com/saltstack/salt/pull/25461 +.. _`#25464`: https://github.com/saltstack/salt/pull/25464 +.. _`#25465`: https://github.com/saltstack/salt/pull/25465 +.. _`#25467`: https://github.com/saltstack/salt/pull/25467 +.. _`#25468`: https://github.com/saltstack/salt/pull/25468 +.. _`#25478`: https://github.com/saltstack/salt/issues/25478 +.. _`#25479`: https://github.com/saltstack/salt/issues/25479 +.. _`#25482`: https://github.com/saltstack/salt/pull/25482 +.. _`#25483`: https://github.com/saltstack/salt/pull/25483 +.. _`#25485`: https://github.com/saltstack/salt/pull/25485 +.. _`#25486`: https://github.com/saltstack/salt/issues/25486 +.. _`#25487`: https://github.com/saltstack/salt/pull/25487 +.. _`#25493`: https://github.com/saltstack/salt/issues/25493 +.. _`#25498`: https://github.com/saltstack/salt/pull/25498 +.. _`#25506`: https://github.com/saltstack/salt/pull/25506 +.. _`#25511`: https://github.com/saltstack/salt/issues/25511 +.. _`#25513`: https://github.com/saltstack/salt/pull/25513 +.. _`#25516`: https://github.com/saltstack/salt/pull/25516 +.. _`#25517`: https://github.com/saltstack/salt/pull/25517 +.. _`#25519`: https://github.com/saltstack/salt/pull/25519 +.. _`#25521`: https://github.com/saltstack/salt/pull/25521 +.. _`#25525`: https://github.com/saltstack/salt/pull/25525 +.. _`#25528`: https://github.com/saltstack/salt/pull/25528 +.. _`#25529`: https://github.com/saltstack/salt/pull/25529 +.. _`#25530`: https://github.com/saltstack/salt/pull/25530 +.. _`#25531`: https://github.com/saltstack/salt/pull/25531 +.. _`#25532`: https://github.com/saltstack/salt/issues/25532 +.. _`#25533`: https://github.com/saltstack/salt/pull/25533 +.. _`#25538`: https://github.com/saltstack/salt/issues/25538 +.. _`#25540`: https://github.com/saltstack/salt/issues/25540 +.. _`#25542`: https://github.com/saltstack/salt/pull/25542 +.. _`#25551`: https://github.com/saltstack/salt/pull/25551 +.. _`#25554`: https://github.com/saltstack/salt/pull/25554 +.. _`#25556`: https://github.com/saltstack/salt/pull/25556 +.. _`#25559`: https://github.com/saltstack/salt/pull/25559 +.. _`#25560`: https://github.com/saltstack/salt/issues/25560 +.. _`#25561`: https://github.com/saltstack/salt/pull/25561 +.. _`#25563`: https://github.com/saltstack/salt/pull/25563 +.. _`#25568`: https://github.com/saltstack/salt/pull/25568 +.. _`#25573`: https://github.com/saltstack/salt/pull/25573 +.. _`#25576`: https://github.com/saltstack/salt/pull/25576 +.. _`#25577`: https://github.com/saltstack/salt/issues/25577 +.. _`#25580`: https://github.com/saltstack/salt/pull/25580 +.. _`#25584`: https://github.com/saltstack/salt/pull/25584 +.. _`#25589`: https://github.com/saltstack/salt/pull/25589 +.. _`#25590`: https://github.com/saltstack/salt/pull/25590 +.. _`#25598`: https://github.com/saltstack/salt/pull/25598 +.. _`#25603`: https://github.com/saltstack/salt/pull/25603 +.. _`#25604`: https://github.com/saltstack/salt/pull/25604 +.. _`#25608`: https://github.com/saltstack/salt/pull/25608 +.. _`#25609`: https://github.com/saltstack/salt/pull/25609 +.. _`#25616`: https://github.com/saltstack/salt/issues/25616 +.. _`#25618`: https://github.com/saltstack/salt/issues/25618 +.. _`#25624`: https://github.com/saltstack/salt/pull/25624 +.. _`#25625`: https://github.com/saltstack/salt/issues/25625 +.. _`#25633`: https://github.com/saltstack/salt/pull/25633 +.. _`#25638`: https://github.com/saltstack/salt/pull/25638 +.. _`#25642`: https://github.com/saltstack/salt/pull/25642 +.. _`#25644`: https://github.com/saltstack/salt/pull/25644 +.. _`#25645`: https://github.com/saltstack/salt/pull/25645 +.. _`#25648`: https://github.com/saltstack/salt/pull/25648 +.. _`#25650`: https://github.com/saltstack/salt/issues/25650 +.. _`#25656`: https://github.com/saltstack/salt/pull/25656 +.. _`#25657`: https://github.com/saltstack/salt/pull/25657 +.. _`#25659`: https://github.com/saltstack/salt/pull/25659 +.. _`#25660`: https://github.com/saltstack/salt/pull/25660 +.. _`#25661`: https://github.com/saltstack/salt/pull/25661 +.. _`#25662`: https://github.com/saltstack/salt/pull/25662 +.. _`#25665`: https://github.com/saltstack/salt/issues/25665 +.. _`#25666`: https://github.com/saltstack/salt/pull/25666 +.. _`#25671`: https://github.com/saltstack/salt/pull/25671 +.. _`#25674`: https://github.com/saltstack/salt/issues/25674 +.. _`#25675`: https://github.com/saltstack/salt/pull/25675 +.. _`#25676`: https://github.com/saltstack/salt/pull/25676 +.. _`#25677`: https://github.com/saltstack/salt/pull/25677 +.. _`#25680`: https://github.com/saltstack/salt/pull/25680 +.. _`#25682`: https://github.com/saltstack/salt/pull/25682 +.. _`#25685`: https://github.com/saltstack/salt/pull/25685 +.. _`#25688`: https://github.com/saltstack/salt/pull/25688 +.. _`#25689`: https://github.com/saltstack/salt/issues/25689 +.. _`#25694`: https://github.com/saltstack/salt/pull/25694 +.. _`#25695`: https://github.com/saltstack/salt/pull/25695 +.. _`#25696`: https://github.com/saltstack/salt/pull/25696 +.. _`#25698`: https://github.com/saltstack/salt/pull/25698 +.. _`#25699`: https://github.com/saltstack/salt/pull/25699 +.. _`#25701`: https://github.com/saltstack/salt/issues/25701 +.. _`#25702`: https://github.com/saltstack/salt/pull/25702 +.. _`#25703`: https://github.com/saltstack/salt/pull/25703 +.. _`#25704`: https://github.com/saltstack/salt/pull/25704 +.. _`#25705`: https://github.com/saltstack/salt/pull/25705 +.. _`#25709`: https://github.com/saltstack/salt/pull/25709 +.. _`#25710`: https://github.com/saltstack/salt/pull/25710 +.. _`#25711`: https://github.com/saltstack/salt/pull/25711 +.. _`#25714`: https://github.com/saltstack/salt/pull/25714 +.. _`#25717`: https://github.com/saltstack/salt/issues/25717 +.. _`#25722`: https://github.com/saltstack/salt/pull/25722 +.. _`#25730`: https://github.com/saltstack/salt/pull/25730 +.. _`#25733`: https://github.com/saltstack/salt/pull/25733 +.. _`#25737`: https://github.com/saltstack/salt/pull/25737 +.. _`#25738`: https://github.com/saltstack/salt/pull/25738 +.. _`#25739`: https://github.com/saltstack/salt/pull/25739 +.. _`#25740`: https://github.com/saltstack/salt/pull/25740 +.. _`#25749`: https://github.com/saltstack/salt/pull/25749 +.. _`#25750`: https://github.com/saltstack/salt/pull/25750 +.. _`#25751`: https://github.com/saltstack/salt/issues/25751 +.. _`#25752`: https://github.com/saltstack/salt/pull/25752 +.. _`#25755`: https://github.com/saltstack/salt/pull/25755 +.. _`#25763`: https://github.com/saltstack/salt/pull/25763 +.. _`#25788`: https://github.com/saltstack/salt/pull/25788 +.. _`#25792`: https://github.com/saltstack/salt/pull/25792 +.. _`#25793`: https://github.com/saltstack/salt/pull/25793 +.. _`#25796`: https://github.com/saltstack/salt/pull/25796 +.. _`#25797`: https://github.com/saltstack/salt/pull/25797 +.. _`#25798`: https://github.com/saltstack/salt/pull/25798 +.. _`#25801`: https://github.com/saltstack/salt/issues/25801 +.. _`#25802`: https://github.com/saltstack/salt/issues/25802 +.. _`#25807`: https://github.com/saltstack/salt/pull/25807 +.. _`#25809`: https://github.com/saltstack/salt/issues/25809 +.. _`#25810`: https://github.com/saltstack/salt/issues/25810 +.. _`#25818`: https://github.com/saltstack/salt/pull/25818 +.. _`#25824`: https://github.com/saltstack/salt/pull/25824 +.. _`#25826`: https://github.com/saltstack/salt/pull/25826 +.. _`#25827`: https://github.com/saltstack/salt/issues/25827 +.. _`#25829`: https://github.com/saltstack/salt/pull/25829 +.. _`#25831`: https://github.com/saltstack/salt/pull/25831 +.. _`#25833`: https://github.com/saltstack/salt/pull/25833 +.. _`#25838`: https://github.com/saltstack/salt/issues/25838 +.. _`#25839`: https://github.com/saltstack/salt/issues/25839 +.. _`#25840`: https://github.com/saltstack/salt/pull/25840 +.. _`#25846`: https://github.com/saltstack/salt/pull/25846 +.. _`#25848`: https://github.com/saltstack/salt/pull/25848 +.. _`#25850`: https://github.com/saltstack/salt/issues/25850 +.. _`#25852`: https://github.com/saltstack/salt/issues/25852 +.. _`#25853`: https://github.com/saltstack/salt/pull/25853 +.. _`#25855`: https://github.com/saltstack/salt/pull/25855 +.. _`#25856`: https://github.com/saltstack/salt/pull/25856 +.. _`#25862`: https://github.com/saltstack/salt/pull/25862 +.. _`#25863`: https://github.com/saltstack/salt/issues/25863 +.. _`#25864`: https://github.com/saltstack/salt/pull/25864 +.. _`#25869`: https://github.com/saltstack/salt/pull/25869 +.. _`#25870`: https://github.com/saltstack/salt/pull/25870 +.. _`#25871`: https://github.com/saltstack/salt/pull/25871 +.. _`#25873`: https://github.com/saltstack/salt/pull/25873 +.. _`#25875`: https://github.com/saltstack/salt/pull/25875 +.. _`#25877`: https://github.com/saltstack/salt/pull/25877 +.. _`#25885`: https://github.com/saltstack/salt/pull/25885 +.. _`#25890`: https://github.com/saltstack/salt/pull/25890 +.. _`#25892`: https://github.com/saltstack/salt/pull/25892 +.. _`#25894`: https://github.com/saltstack/salt/pull/25894 +.. _`#25895`: https://github.com/saltstack/salt/pull/25895 +.. _`#25898`: https://github.com/saltstack/salt/pull/25898 +.. _`#25905`: https://github.com/saltstack/salt/pull/25905 +.. _`#25915`: https://github.com/saltstack/salt/issues/25915 +.. _`#25917`: https://github.com/saltstack/salt/pull/25917 +.. _`#25919`: https://github.com/saltstack/salt/pull/25919 +.. _`#25921`: https://github.com/saltstack/salt/pull/25921 +.. _`#25927`: https://github.com/saltstack/salt/pull/25927 +.. _`#25938`: https://github.com/saltstack/salt/pull/25938 +.. _`#25941`: https://github.com/saltstack/salt/pull/25941 +.. _`#25942`: https://github.com/saltstack/salt/pull/25942 +.. _`#25948`: https://github.com/saltstack/salt/issues/25948 +.. _`#25949`: https://github.com/saltstack/salt/issues/25949 +.. _`#25951`: https://github.com/saltstack/salt/pull/25951 +.. _`#25958`: https://github.com/saltstack/salt/issues/25958 +.. _`#25961`: https://github.com/saltstack/salt/issues/25961 +.. _`#25966`: https://github.com/saltstack/salt/pull/25966 +.. _`#25967`: https://github.com/saltstack/salt/pull/25967 +.. _`#25970`: https://github.com/saltstack/salt/pull/25970 +.. _`#25971`: https://github.com/saltstack/salt/pull/25971 +.. _`#25976`: https://github.com/saltstack/salt/pull/25976 +.. _`#25982`: https://github.com/saltstack/salt/issues/25982 +.. _`#25983`: https://github.com/saltstack/salt/issues/25983 +.. _`#25984`: https://github.com/saltstack/salt/pull/25984 +.. _`#25990`: https://github.com/saltstack/salt/pull/25990 +.. _`#25992`: https://github.com/saltstack/salt/pull/25992 +.. _`#25994`: https://github.com/saltstack/salt/issues/25994 +.. _`#25996`: https://github.com/saltstack/salt/pull/25996 +.. _`#25998`: https://github.com/saltstack/salt/issues/25998 +.. _`#26000`: https://github.com/saltstack/salt/pull/26000 +.. _`#26002`: https://github.com/saltstack/salt/pull/26002 +.. _`#26016`: https://github.com/saltstack/salt/pull/26016 +.. _`#26020`: https://github.com/saltstack/salt/pull/26020 +.. _`#26021`: https://github.com/saltstack/salt/pull/26021 +.. _`#26024`: https://github.com/saltstack/salt/issues/26024 +.. _`#26030`: https://github.com/saltstack/salt/pull/26030 +.. _`#26031`: https://github.com/saltstack/salt/pull/26031 +.. _`#26032`: https://github.com/saltstack/salt/pull/26032 +.. _`#26036`: https://github.com/saltstack/salt/pull/26036 +.. _`#26039`: https://github.com/saltstack/salt/issues/26039 +.. _`#26042`: https://github.com/saltstack/salt/pull/26042 +.. _`#26044`: https://github.com/saltstack/salt/pull/26044 +.. _`#26047`: https://github.com/saltstack/salt/pull/26047 +.. _`#26048`: https://github.com/saltstack/salt/pull/26048 +.. _`#26058`: https://github.com/saltstack/salt/pull/26058 +.. _`#26061`: https://github.com/saltstack/salt/pull/26061 +.. _`#26063`: https://github.com/saltstack/salt/issues/26063 +.. _`#26064`: https://github.com/saltstack/salt/pull/26064 +.. _`#26065`: https://github.com/saltstack/salt/pull/26065 +.. _`#26068`: https://github.com/saltstack/salt/pull/26068 +.. _`#26079`: https://github.com/saltstack/salt/pull/26079 +.. _`#26080`: https://github.com/saltstack/salt/pull/26080 +.. _`#26084`: https://github.com/saltstack/salt/pull/26084 +.. _`#26088`: https://github.com/saltstack/salt/pull/26088 +.. _`#26093`: https://github.com/saltstack/salt/issues/26093 +.. _`#26098`: https://github.com/saltstack/salt/issues/26098 +.. _`#26101`: https://github.com/saltstack/salt/pull/26101 +.. _`#26106`: https://github.com/saltstack/salt/pull/26106 +.. _`#26110`: https://github.com/saltstack/salt/pull/26110 +.. _`#26111`: https://github.com/saltstack/salt/pull/26111 +.. _`#26112`: https://github.com/saltstack/salt/issues/26112 +.. _`#26116`: https://github.com/saltstack/salt/pull/26116 +.. _`#26119`: https://github.com/saltstack/salt/pull/26119 +.. _`#26127`: https://github.com/saltstack/salt/pull/26127 +.. _`#26132`: https://github.com/saltstack/salt/pull/26132 +.. _`#26133`: https://github.com/saltstack/salt/pull/26133 +.. _`#26135`: https://github.com/saltstack/salt/pull/26135 +.. _`#26137`: https://github.com/saltstack/salt/pull/26137 +.. _`#26140`: https://github.com/saltstack/salt/pull/26140 +.. _`#26141`: https://github.com/saltstack/salt/issues/26141 +.. _`#26147`: https://github.com/saltstack/salt/pull/26147 +.. _`#26153`: https://github.com/saltstack/salt/pull/26153 +.. _`#26162`: https://github.com/saltstack/salt/issues/26162 +.. _`#26163`: https://github.com/saltstack/salt/pull/26163 +.. _`#26168`: https://github.com/saltstack/salt/pull/26168 +.. _`#26172`: https://github.com/saltstack/salt/pull/26172 +.. _`#26175`: https://github.com/saltstack/salt/pull/26175 +.. _`#26177`: https://github.com/saltstack/salt/pull/26177 +.. _`#26179`: https://github.com/saltstack/salt/pull/26179 +.. _`#26180`: https://github.com/saltstack/salt/pull/26180 +.. _`#26182`: https://github.com/saltstack/salt/pull/26182 +.. _`#26183`: https://github.com/saltstack/salt/pull/26183 +.. _`#26186`: https://github.com/saltstack/salt/pull/26186 +.. _`#26207`: https://github.com/saltstack/salt/issues/26207 +.. _`#26219`: https://github.com/saltstack/salt/pull/26219 +.. _`#26232`: https://github.com/saltstack/salt/pull/26232 +.. _`#26237`: https://github.com/saltstack/salt/pull/26237 +.. _`#26239`: https://github.com/saltstack/salt/pull/26239 +.. _`#26246`: https://github.com/saltstack/salt/pull/26246 +.. _`#26247`: https://github.com/saltstack/salt/pull/26247 +.. _`#26257`: https://github.com/saltstack/salt/pull/26257 +.. _`#26258`: https://github.com/saltstack/salt/pull/26258 +.. _`#26261`: https://github.com/saltstack/salt/pull/26261 +.. _`#26263`: https://github.com/saltstack/salt/pull/26263 +.. _`#26265`: https://github.com/saltstack/salt/pull/26265 +.. _`#26268`: https://github.com/saltstack/salt/pull/26268 +.. _`#26271`: https://github.com/saltstack/salt/pull/26271 +.. _`#26273`: https://github.com/saltstack/salt/pull/26273 +.. _`#26275`: https://github.com/saltstack/salt/pull/26275 +.. _`#26285`: https://github.com/saltstack/salt/pull/26285 +.. _`#26288`: https://github.com/saltstack/salt/pull/26288 +.. _`#26290`: https://github.com/saltstack/salt/pull/26290 +.. _`#26292`: https://github.com/saltstack/salt/pull/26292 +.. _`#26293`: https://github.com/saltstack/salt/pull/26293 +.. _`#26296`: https://github.com/saltstack/salt/pull/26296 +.. _`#3`: https://github.com/saltstack/salt/issues/3 +.. _`#455`: https://github.com/saltstack/salt/issues/455 +.. _`#598`: https://github.com/saltstack/salt/issues/598 +.. _`#602`: https://github.com/saltstack/salt/pull/602 +.. _`#606`: https://github.com/saltstack/salt/pull/606 +.. _`#607`: https://github.com/saltstack/salt/issues/607 +.. _`#611`: https://github.com/saltstack/salt/issues/611 +.. _`#621`: https://github.com/saltstack/salt/pull/621 +.. _`#624`: https://github.com/saltstack/salt/pull/624 +.. _`#625`: https://github.com/saltstack/salt/issues/625 +.. _`#627`: https://github.com/saltstack/salt/pull/627 +.. _`#630`: https://github.com/saltstack/salt/issues/630 +.. _`#631`: https://github.com/saltstack/salt/issues/631 +.. _`#632`: https://github.com/saltstack/salt/pull/632 +.. _`#633`: https://github.com/saltstack/salt/pull/633 +.. _`#634`: https://github.com/saltstack/salt/issues/634 +.. _`#638`: https://github.com/saltstack/salt/pull/638 +.. _`#640`: https://github.com/saltstack/salt/pull/640 +.. _`bp-20972`: https://github.com/saltstack/salt/pull/20972 +.. _`bp-24054`: https://github.com/saltstack/salt/pull/24054 +.. _`bp-24982`: https://github.com/saltstack/salt/pull/24982 +.. _`bp-25001`: https://github.com/saltstack/salt/pull/25001 +.. _`bp-25019`: https://github.com/saltstack/salt/pull/25019 +.. _`bp-25020`: https://github.com/saltstack/salt/pull/25020 +.. _`bp-25059`: https://github.com/saltstack/salt/pull/25059 +.. _`bp-25088`: https://github.com/saltstack/salt/pull/25088 +.. _`bp-25102`: https://github.com/saltstack/salt/pull/25102 +.. _`bp-25128`: https://github.com/saltstack/salt/pull/25128 +.. _`bp-25256`: https://github.com/saltstack/salt/pull/25256 +.. _`bp-25290`: https://github.com/saltstack/salt/pull/25290 +.. _`bp-25309`: https://github.com/saltstack/salt/pull/25309 +.. _`bp-25389`: https://github.com/saltstack/salt/pull/25389 +.. _`bp-25399`: https://github.com/saltstack/salt/pull/25399 +.. _`bp-25404`: https://github.com/saltstack/salt/pull/25404 +.. _`bp-25464`: https://github.com/saltstack/salt/pull/25464 +.. _`bp-25483`: https://github.com/saltstack/salt/pull/25483 +.. _`bp-25485`: https://github.com/saltstack/salt/pull/25485 +.. _`bp-25529`: https://github.com/saltstack/salt/pull/25529 +.. _`bp-25530`: https://github.com/saltstack/salt/pull/25530 +.. _`bp-25608`: https://github.com/saltstack/salt/pull/25608 +.. _`bp-25624`: https://github.com/saltstack/salt/pull/25624 +.. _`bp-25638`: https://github.com/saltstack/salt/pull/25638 +.. _`bp-25660`: https://github.com/saltstack/salt/pull/25660 +.. _`bp-25671`: https://github.com/saltstack/salt/pull/25671 +.. _`bp-25688`: https://github.com/saltstack/salt/pull/25688 +.. _`bp-25696`: https://github.com/saltstack/salt/pull/25696 +.. _`bp-25709`: https://github.com/saltstack/salt/pull/25709 +.. _`bp-25722`: https://github.com/saltstack/salt/pull/25722 +.. _`bp-25730`: https://github.com/saltstack/salt/pull/25730 +.. _`bp-25788`: https://github.com/saltstack/salt/pull/25788 +.. _`bp-25824`: https://github.com/saltstack/salt/pull/25824 +.. _`bp-25829`: https://github.com/saltstack/salt/pull/25829 +.. _`bp-25855`: https://github.com/saltstack/salt/pull/25855 +.. _`bp-25862`: https://github.com/saltstack/salt/pull/25862 +.. _`bp-25864`: https://github.com/saltstack/salt/pull/25864 +.. _`bp-25892`: https://github.com/saltstack/salt/pull/25892 +.. _`bp-25917`: https://github.com/saltstack/salt/pull/25917 +.. _`bp-25976`: https://github.com/saltstack/salt/pull/25976 +.. _`bp-25984`: https://github.com/saltstack/salt/pull/25984 +.. _`bp-26147`: https://github.com/saltstack/salt/pull/26147 +.. _`bp-26153`: https://github.com/saltstack/salt/pull/26153 +.. _`bp-26237`: https://github.com/saltstack/salt/pull/26237 +.. _`fix-11474`: https://github.com/saltstack/salt/issues/11474 +.. _`fix-2015`: https://github.com/saltstack/salt/pull/2015 +.. _`fix-22699`: https://github.com/saltstack/salt/issues/22699 +.. _`fix-24036`: https://github.com/saltstack/salt/issues/24036 +.. _`fix-24272`: https://github.com/saltstack/salt/issues/24272 +.. _`fix-24483`: https://github.com/saltstack/salt/issues/24483 +.. _`fix-24484`: https://github.com/saltstack/salt/issues/24484 +.. _`fix-24882`: https://github.com/saltstack/salt/issues/24882 +.. _`fix-25192`: https://github.com/saltstack/salt/issues/25192 +.. _`fix-25616`: https://github.com/saltstack/salt/issues/25616 +.. _`fix-26163`: https://github.com/saltstack/salt/pull/26163 diff --git a/pkg/rpm/salt-api b/pkg/rpm/salt-api index e4dfc3e49b4..0d172bb3b14 100755 --- a/pkg/rpm/salt-api +++ b/pkg/rpm/salt-api @@ -73,7 +73,10 @@ start() { RETVAL=1 else daemon --pidfile=$PID_FILE --check $SERVICE $SALTAPI $CONFIG_ARGS - RETVAL=0 + RETVAL=$? + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE + echo + return $RETVAL fi fi RETVAL=$? @@ -97,6 +100,10 @@ stop() { fi else killproc $PROCESS + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$SERVICE + return $RETVAL fi RETVAL=$? echo diff --git a/pkg/rpm/salt-master b/pkg/rpm/salt-master index a05ef357fb2..dd1a1afc702 100755 --- a/pkg/rpm/salt-master +++ b/pkg/rpm/salt-master @@ -64,6 +64,10 @@ start() { fi else daemon --check $SERVICE $SALTMASTER -d $MASTER_ARGS + RETVAL=$? + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE + echo + return $RETVAL fi RETVAL=$? echo @@ -86,6 +90,10 @@ stop() { fi else killproc $PROCESS + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$SERVICE + return $RETVAL fi RETVAL=$? echo diff --git a/pkg/rpm/salt-minion b/pkg/rpm/salt-minion index 214f895a9d7..9a4796f2582 100755 --- a/pkg/rpm/salt-minion +++ b/pkg/rpm/salt-minion @@ -68,7 +68,11 @@ start() { RETVAL=$? echo -n "already running" else - daemon --check $SERVICE $SALTMINION -d $MINION_ARGS + daemon --check $SERVICE $SALTMINION -d $MINION_ARGS + RETVAL=$? + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE + echo + return $RETVAL fi fi RETVAL=$? @@ -94,6 +98,7 @@ stop() { else killproc $PROCESS RETVAL=$? + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$SERVICE # tidy up any rogue processes: PROCS=`ps -ef | grep "$SALTMINION" | grep -v grep | awk '{print $2}'` if [ -n "$PROCS" ]; then diff --git a/pkg/rpm/salt-syndic b/pkg/rpm/salt-syndic index a687e3dcded..fa0493401eb 100755 --- a/pkg/rpm/salt-syndic +++ b/pkg/rpm/salt-syndic @@ -65,6 +65,10 @@ start() { fi else daemon --check $SERVICE $SALTSYNDIC -d $SYNDIC_ARGS + RETVAL=$? + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE + echo + return $RETVAL fi RETVAL=$? echo @@ -87,6 +91,10 @@ stop() { fi else killproc $PROCESS + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$SERVICE + return $RETVAL fi RETVAL=$? echo diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py index 2d769419e07..4f651f0f45a 100644 --- a/salt/client/ssh/__init__.py +++ b/salt/client/ssh/__init__.py @@ -954,10 +954,10 @@ ARGS = {9}\n'''.format(self.minion_config, pass # Execute shim - ret = self.shell.exec_cmd('/bin/sh $HOME/{0}'.format(target_shim_file)) + ret = self.shell.exec_cmd('/bin/sh \'$HOME/{0}\''.format(target_shim_file)) # Remove shim from target system - self.shell.exec_cmd('rm $HOME/{0}'.format(target_shim_file)) + self.shell.exec_cmd('rm \'$HOME/{0}\''.format(target_shim_file)) return ret diff --git a/salt/cloud/clouds/ec2.py b/salt/cloud/clouds/ec2.py index a071b82365c..c233394a6b2 100644 --- a/salt/cloud/clouds/ec2.py +++ b/salt/cloud/clouds/ec2.py @@ -1654,7 +1654,6 @@ def request_instance(vm_=None, call=None): } try: rd_data = aws.query(rd_params, - return_root=True, location=get_location(), provider=get_provider(), opts=__opts__, @@ -2363,7 +2362,7 @@ def create(vm_=None, call=None): 'volumes': volumes, 'zone': ret['placement']['availabilityZone'], 'instance_id': ret['instanceId'], - 'del_all_vols_on_destroy': vm_.get('set_del_all_vols_on_destroy', False) + 'del_all_vols_on_destroy': vm_.get('del_all_vols_on_destroy', False) }, call='action' ) @@ -3780,38 +3779,52 @@ def delete_keypair(kwargs=None, call=None): def create_snapshot(kwargs=None, call=None, wait_to_finish=False): ''' - Create a snapshot + Create a snapshot. + + volume_id + The ID of the Volume from which to create a snapshot. + + description + The optional description of the snapshot. + + CLI Exampe: + + .. code-block:: bash + + salt-cloud -f create_snapshot my-ec2-config volume_id=vol-351d8826 + salt-cloud -f create_snapshot my-ec2-config volume_id=vol-351d8826 \\ + description="My Snapshot Description" ''' if call != 'function': - log.error( + raise SaltCloudSystemExit( 'The create_snapshot function must be called with -f ' 'or --function.' ) - return False - if 'volume_id' not in kwargs: - log.error('A volume_id must be specified to create a snapshot.') - return False + if kwargs is None: + kwargs = {} - if 'description' not in kwargs: - kwargs['description'] = '' + volume_id = kwargs.get('volume_id', None) + description = kwargs.get('description', '') - params = {'Action': 'CreateSnapshot'} + if volume_id is None: + raise SaltCloudSystemExit( + 'A volume_id must be specified to create a snapshot.' + ) - if 'volume_id' in kwargs: - params['VolumeId'] = kwargs['volume_id'] - - if 'description' in kwargs: - params['Description'] = kwargs['description'] + params = {'Action': 'CreateSnapshot', + 'VolumeId': volume_id, + 'Description': description} log.debug(params) data = aws.query(params, return_url=True, + return_root=True, location=get_location(), provider=get_provider(), opts=__opts__, - sigver='4') + sigver='4')[0] r_data = {} for d in data: @@ -3827,7 +3840,7 @@ def create_snapshot(kwargs=None, call=None, wait_to_finish=False): argument_being_watched='status', required_argument_response='completed') - return data + return r_data def delete_snapshot(kwargs=None, call=None): diff --git a/salt/daemons/masterapi.py b/salt/daemons/masterapi.py index 5c41623278a..a1da41db06d 100644 --- a/salt/daemons/masterapi.py +++ b/salt/daemons/masterapi.py @@ -348,7 +348,7 @@ class AutoKey(object): autosign_dir = os.path.join(self.opts['pki_dir'], 'minions_autosign') # cleanup expired files - expire_minutes = self.opts.get('autosign_expire_minutes', 10) + expire_minutes = self.opts.get('autosign_timeout', 120) if expire_minutes > 0: min_time = time.time() - (60 * int(expire_minutes)) for root, dirs, filenames in os.walk(autosign_dir): diff --git a/salt/minion.py b/salt/minion.py index 06b1f0f249b..54e2bc3a174 100644 --- a/salt/minion.py +++ b/salt/minion.py @@ -1558,6 +1558,7 @@ class Minion(MinionBase): del self.pub_channel self._connect_master_future = self.connect_master() self.block_until_connected() # TODO: remove + self.functions, self.returners, self.function_errors = self._load_modules() self._fire_master_minion_start() log.info('Minion is ready to receive requests!') diff --git a/salt/modules/boto_cloudwatch.py b/salt/modules/boto_cloudwatch.py index 21d5a1a2ab5..3343a0df9b8 100644 --- a/salt/modules/boto_cloudwatch.py +++ b/salt/modules/boto_cloudwatch.py @@ -231,12 +231,25 @@ def create_or_update_alarm( if isinstance(ok_actions, string_types): ok_actions = ok_actions.split(",") - # convert action names into ARN's - alarm_actions = convert_to_arn(alarm_actions, region, key, keyid, profile) - insufficient_data_actions = convert_to_arn( - insufficient_data_actions, region, key, keyid, profile - ) - ok_actions = convert_to_arn(ok_actions, region, key, keyid, profile) + # convert provided action names into ARN's + if alarm_actions: + alarm_actions = convert_to_arn(alarm_actions, + region=region, + key=key, + keyid=keyid, + profile=profile) + if insufficient_data_actions: + insufficient_data_actions = convert_to_arn(insufficient_data_actions, + region=region, + key=key, + keyid=keyid, + profile=profile) + if ok_actions: + ok_actions = convert_to_arn(ok_actions, + region=region, + key=key, + keyid=keyid, + profile=profile) conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile) diff --git a/salt/modules/cron.py b/salt/modules/cron.py index a0da72bbe1d..72a430e62d1 100644 --- a/salt/modules/cron.py +++ b/salt/modules/cron.py @@ -276,14 +276,20 @@ def list_tab(user): ret['special'].append(dat) elif line.startswith('#'): # It's a comment! Catch it! - comment = line.lstrip('# ') + comment_line = line.lstrip('# ') + # load the identifier if any - if SALT_CRON_IDENTIFIER in comment: - parts = comment.split(SALT_CRON_IDENTIFIER) - comment = parts[0].rstrip() + if SALT_CRON_IDENTIFIER in comment_line: + parts = comment_line.split(SALT_CRON_IDENTIFIER) + comment_line = parts[0].rstrip() # skip leading : if len(parts[1]) > 1: identifier = parts[1][1:] + + if comment is None: + comment = comment_line + else: + comment += '\n' + comment_line elif len(line.split()) > 5: # Appears to be a standard cron line comps = line.split() diff --git a/salt/modules/gpg.py b/salt/modules/gpg.py index 26df62a8880..eb957790cde 100644 --- a/salt/modules/gpg.py +++ b/salt/modules/gpg.py @@ -26,6 +26,14 @@ from salt.exceptions import SaltInvocationError # Import 3rd-party libs import salt.ext.six as six +try: + from shlex import quote as _cmd_quote # pylint: disable=E0611 +except ImportError: + from pipes import quote as _cmd_quote + +from salt.exceptions import ( + SaltInvocationError +) # Set up logging log = logging.getLogger(__name__) @@ -826,15 +834,15 @@ def trust_key(keyid=None, if trust_level not in _VALID_TRUST_LEVELS: return 'ERROR: Valid trust levels - {0}'.format(','.join(_VALID_TRUST_LEVELS)) - cmd = 'echo {0}:{1} | {2} --import-ownertrust'.format(fingerprint, - NUM_TRUST_DICT[trust_level], - _check_gpg()) + cmd = 'echo {0}:{1} | {2} --import-ownertrust'.format(_cmd_quote(fingerprint), + _cmd_quote(NUM_TRUST_DICT[trust_level]), + _cmd_quote(_check_gpg())) _user = user if user == 'salt': homeDir = os.path.join(salt.syspaths.CONFIG_DIR, 'gpgkeys') cmd = '{0} --homedir {1}'.format(cmd, homeDir) _user = 'root' - res = __salt__['cmd.run_all'](cmd, runas=_user) + res = __salt__['cmd.run_all'](cmd, runas=_user, python_shell=True) if not res['retcode'] == 0: ret['res'] = False diff --git a/salt/modules/keystone.py b/salt/modules/keystone.py index 696ec33dcf2..7fa14e2ca47 100644 --- a/salt/modules/keystone.py +++ b/salt/modules/keystone.py @@ -51,6 +51,7 @@ Module for handling openstack keystone calls. # Import Python libs from __future__ import absolute_import +import logging # Import Salt Libs import salt.ext.six as six @@ -66,6 +67,8 @@ try: except ImportError: pass +log = logging.getLogger(__name__) + def __virtual__(): ''' @@ -707,7 +710,13 @@ def user_get(user_id=None, name=None, profile=None, **connection_args): break if not user_id: return {'Error': 'Unable to resolve user id'} - user = kstone.users.get(user_id) + try: + user = kstone.users.get(user_id) + except keystoneclient.exceptions.NotFound: + msg = 'Could not find user \'{0}\''.format(user_id) + log.error(msg) + return {'Error': msg} + ret[user.name] = {'id': user.id, 'name': user.name, 'email': user.email, diff --git a/salt/modules/linux_lvm.py b/salt/modules/linux_lvm.py index c18c751e76e..70094812c19 100644 --- a/salt/modules/linux_lvm.py +++ b/salt/modules/linux_lvm.py @@ -391,7 +391,7 @@ def vgremove(vgname): salt mymachine lvm.vgremove vgname salt mymachine lvm.vgremove vgname force=True ''' - cmd = ['vgremove' '-f', vgname] + cmd = ['vgremove', '-f', vgname] out = __salt__['cmd.run'](cmd, python_shell=False) return out.strip() diff --git a/salt/modules/pw_group.py b/salt/modules/pw_group.py index e27ba8d3297..430f2ff8ad3 100644 --- a/salt/modules/pw_group.py +++ b/salt/modules/pw_group.py @@ -130,3 +130,23 @@ def chgid(name, gid): if post_gid != pre_gid: return post_gid == gid return False + + +def members(name, members_list): + ''' + Replaces members of the group with a provided list. + + .. versionadded:: 2015.5.4 + + CLI Example: + + salt '*' group.members foo 'user1,user2,user3,...' + + Replaces a membership list for a local group 'foo'. + foo:x:1234:user1,user2,user3,... + ''' + + retcode = __salt__['cmd.retcode']('pw groupmod {0} -M {1}'.format( + name, members_list), python_shell=False) + + return not retcode diff --git a/salt/modules/pw_user.py b/salt/modules/pw_user.py index dee4bc60e57..471c9bb3cfa 100644 --- a/salt/modules/pw_user.py +++ b/salt/modules/pw_user.py @@ -9,8 +9,9 @@ import copy import logging try: import pwd + HAS_PWD = True except ImportError: - pass + HAS_PWD = False # Import 3rd party libs import salt.ext.six as six @@ -29,7 +30,9 @@ def __virtual__(): ''' Set the user module if the kernel is FreeBSD ''' - return __virtualname__ if __grains__['kernel'] == 'FreeBSD' else False + if HAS_PWD and __grains__['kernel'] == 'FreeBSD': + return __virtualname__ + return False def _get_gecos(name): diff --git a/salt/modules/reg.py b/salt/modules/reg.py index 7029b196b9c..4595679b8e4 100644 --- a/salt/modules/reg.py +++ b/salt/modules/reg.py @@ -1,29 +1,36 @@ # -*- coding: utf-8 -*- ''' -Manage the registry on Windows. +=========================== +Manage the Windows registry +=========================== The read_key and set_key functions will be updated in Boron to reflect proper registry usage. The registry has three main components. Hives, Keys, and Values. -### Hives +----- +Hives +----- Hives are the main sections of the registry and all begin with the word HKEY. - HKEY_LOCAL_MACHINE - HKEY_CURRENT_USER - HKEY_USER -### Keys +---- +Keys +---- Keys are the folders in the registry. Keys can have many nested subkeys. Keys can have a value assigned to them under the (Default) -### Values -Values are name/data pairs. There can be many values in a key. The (Default) -value corresponds to the Key, the rest are their own value pairs. +----------------- +Values or Entries +----------------- +Values/Entries are name/data pairs. There can be many values in a key. The +(Default) value corresponds to the Key, the rest are their own value pairs. :depends: - winreg Python module ''' -# TODO: Figure out the exceptions _winreg can raise and properly catch -# them instead of a bare except that catches any exception at all +# TODO: Figure out the exceptions _winreg can raise and properly catch them # Import python libs from __future__ import absolute_import @@ -142,44 +149,35 @@ def read_key(hkey, path, key=None): key=path, vname=key) - registry = Registry() - hive = registry.hkeys[hkey] - - try: - value = _winreg.QueryValue(hive, path) - if value: - ret['vdata'] = value - else: - ret['vdata'] = None - ret['comment'] = 'Empty Value' - except WindowsError as exc: # pylint: disable=E0602 - log.debug(exc) - ret['comment'] = '{0}'.format(exc) - ret['success'] = False - - return ret + return read_value(hive=hkey, key=path) def read_value(hive, key, vname=None): r''' - Reads a registry value or the default value for a key. + Reads a registry value entry or the default value for a key. - :param hive: string - 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 + - HKEY_LOCAL_MACHINE or HKLM + - HKEY_CURRENT_USER or HKCU + - HKEY_USER or HKU - :param key: string - The key (looks like a path) to the value name. + :param str key: + The key (looks like a path) to the value name. - :param vname: string - 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 vname: + The value name. These are the individual name/data pairs under the key. + If not passed, the key (Default) value will be returned - :return: dict - A dictionary containing the passed settings as well as the value_data if - successful. If unsuccessful, sets success to False + :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. + :rtype: dict CLI Example: @@ -205,9 +203,9 @@ def read_value(hive, key, vname=None): try: handle = _winreg.OpenKey(hive, key) - value, vtype = _winreg.QueryValueEx(handle, vname) - if value: - ret['vdata'] = value + vdata, vtype = _winreg.QueryValueEx(handle, vname) + if vdata: + ret['vdata'] = vdata ret['vtype'] = registry.vtype_reverse[vtype] else: ret['comment'] = 'Empty Value' @@ -257,53 +255,45 @@ def set_key(hkey, path, value, key=None, vtype='REG_DWORD', reflection=True): vdata=value, vtype=vtype) - registry = Registry() - hive = registry.hkeys[hkey] - vtype = registry.vtype['REG_SZ'] - - try: - _winreg.SetValue(hive, path, vtype, value) - return True - except WindowsError as exc: # pylint: disable=E0602 - log.error(exc) - return False + return set_value(hive=hkey, key=path, vdata=value, vtype=vtype) def set_value(hive, key, vname=None, vdata=None, vtype='REG_SZ', reflection=True): ''' - Sets a registry value. + Sets a registry value entry or the default value for a key. - :param hive: string - 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 + - HKEY_LOCAL_MACHINE or HKLM + - HKEY_CURRENT_USER or HKCU + - HKEY_USER or HKU - :param key: string - The key (looks like a path) to the value name. + :param str key: + The key (looks like a path) to the value name. - :param vname: string - 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 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 vdata: string - The value data to be set. + :param str vdata: + The value data to be set. - :param vtype: string - The value type. Can be one of the following: - - REG_BINARY - - REG_DWORD - - REG_EXPAND_SZ - - REG_MULTI_SZ - - REG_SZ + :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 reflection: boolean - 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 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. - :return: boolean - Returns True if successful, False if not + :return: + Returns True if successful, False if not + :rtype: bool CLI Example: @@ -321,7 +311,7 @@ def set_value(hive, key, vname=None, vdata=None, vtype='REG_SZ', reflection=True _winreg.SetValueEx(handle, vname, 0, vtype, vdata) _winreg.CloseKey(handle) return True - except WindowsError as exc: # pylint: disable=E0602 + except (WindowsError, ValueError) as exc: # pylint: disable=E0602 log.error(exc) return False @@ -353,7 +343,7 @@ def create_key(hkey, path, key=None, value=None, reflection=True): salt '*' reg.create_key HKEY_CURRENT_USER 'SOFTWARE\\Salt' 'version' '0.97' ''' if key: # This if statement will be removed in Boron - salt.utils.warn_until('Boron', 'Use reg.set_value to set a registry ' + salt.utils.warn_until('Boron', 'Use reg.set_value to create a registry ' 'value. This functionality will be ' 'removed in Salt Boron') return set_value(hive=hkey, @@ -362,21 +352,10 @@ def create_key(hkey, path, key=None, value=None, reflection=True): vdata=value, vtype='REG_SZ') - registry = Registry() - hive = registry.hkeys[hkey] - key = path - access_mask = registry.reflection_mask[reflection] - - try: - handle = _winreg.CreateKeyEx(hive, key, 0, access_mask) - _winreg.CloseKey(handle) - return True - except WindowsError as exc: # pylint: disable=E0602 - log.error(exc) - return False + return set_value(hive=hkey, key=path) -def delete_key(hkey, path, key=None, reflection=True): +def delete_key(hkey, path, key=None, reflection=True, force=False): ''' *** Incorrect Usage *** The name of this function is misleading and will be changed to reflect @@ -396,29 +375,62 @@ def delete_key(hkey, path, key=None, reflection=True): Delete a registry key - Note: This cannot delete a key with subkeys - CLI Example: .. code-block:: bash 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 path: (will be changed to key) + The key (looks like a path) to remove. + + :param str key: (used incorrectly) + Will be removed 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. + + Only applies to delete value. If the key parameter is passed, this + function calls delete_value instead. Will be changed in Boron. + + :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 ''' if key: # This if statement will be removed in Boron - salt.utils.warn_until('Boron', 'Use reg.set_value to set a registry ' - 'value. This functionality will be ' - 'removed in Salt Boron') + salt.utils.warn_until('Boron', + 'Variable names will be changed to match Windows ' + 'Registry terminology. These changes will be ' + 'made in Boron') return delete_value(hive=hkey, key=path, vname=key, reflection=reflection) + if force: + return delete_key_recursive(hkey, path) + registry = Registry() hive = registry.hkeys[hkey] key = path try: + # Can't use delete_value to delete a key _winreg.DeleteKey(hive, key) return True except WindowsError as exc: # pylint: disable=E0602 @@ -426,30 +438,102 @@ def delete_key(hkey, path, key=None, reflection=True): return False +def delete_key_recursive(hive, key): + ''' + .. 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 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 + ''' + # Functions for traversing the registry tree + def subkeys(key): + i = 0 + while True: + try: + subkey = _winreg.EnumKey(key, i) + yield subkey + i += 1 + except WindowsError: # pylint: disable=E0602 + break + + def traverse_registry_tree(hkey, keypath, ret): + key = _winreg.OpenKey(hkey, keypath, 0, _winreg.KEY_READ) + for subkeyname in subkeys(key): + subkeypath = r'{0}\{1}'.format(keypath, subkeyname) + ret = traverse_registry_tree(hkey, subkeypath, ret) + 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) + + ret = {'Deleted': [], + 'Failed': []} + + # Delete all subkeys + for keypath in key_list: + try: + _winreg.DeleteKey(hkey, keypath) + ret['Deleted'].append(r'{0}\{1}'.format(hive, keypath)) + except WindowsError as exc: # pylint: disable=E0602 + log.error(exc) + 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) + ret['Failed'].append(r'{0}\{1} {2}'.format(hive, key, exc)) + + return ret + + def delete_value(hive, key, vname=None, reflection=True): ''' - Deletes a registry value. + Delete a registry value entry or the default value for a key. - :param hive: string - 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 + - HKEY_LOCAL_MACHINE or HKLM + - HKEY_CURRENT_USER or HKCU + - HKEY_USER or HKU - :param key: string - The key (looks like a path) to the value name. + :param str key: + The key (looks like a path) to the value name. - :param vname: string - 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 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 reflection: boolean - 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 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. - :return: boolean - Returns True if successful, False if not + :return: + Returns True if successful, False if not + :rtype: bool CLI Example: diff --git a/salt/modules/schedule.py b/salt/modules/schedule.py index b4f5eb684f2..9ce37563092 100644 --- a/salt/modules/schedule.py +++ b/salt/modules/schedule.py @@ -97,6 +97,11 @@ def list_(show_all=False, where=None, return_yaml=True): del schedule[job] continue + # if enabled is not included in the job, + # assume job is enabled. + if 'enabled' not in schedule[job]: + schedule[job]['enabled'] = True + for item in pycopy.copy(schedule[job]): if item not in SCHEDULE_CONF: del schedule[job][item] diff --git a/salt/modules/useradd.py b/salt/modules/useradd.py index 70180f1e802..b63093c803e 100644 --- a/salt/modules/useradd.py +++ b/salt/modules/useradd.py @@ -9,8 +9,9 @@ import re try: import pwd + HAS_PWD = True except ImportError: - pass + HAS_PWD = False import logging import copy @@ -32,10 +33,9 @@ __virtualname__ = 'user' def __virtual__(): ''' Set the user module if the kernel is Linux, OpenBSD or NetBSD - and remove some of the functionality on OS X ''' - if __grains__['kernel'] in ('Linux', 'OpenBSD', 'NetBSD'): + if HAS_PWD and __grains__['kernel'] in ('Linux', 'OpenBSD', 'NetBSD'): return __virtualname__ return False diff --git a/salt/states/boto_elb.py b/salt/states/boto_elb.py index de4746a4365..6f6f08e5bc2 100644 --- a/salt/states/boto_elb.py +++ b/salt/states/boto_elb.py @@ -193,7 +193,11 @@ Overriding the alarm values on the resource: attributes: threshold: 2.0 ''' + +# Import Python Libs from __future__ import absolute_import + +# Import Salt Libs import salt.utils.dictupdate as dictupdate from salt.exceptions import SaltInvocationError import salt.ext.six as six @@ -307,13 +311,17 @@ def present( ret['result'] = _ret['result'] if ret['result'] is False: return ret - _ret = _attributes_present(name, attributes, region, key, keyid, profile) - ret['changes'] = dictupdate.update(ret['changes'], _ret['changes']) - ret['comment'] = ' '.join([ret['comment'], _ret['comment']]) - if not _ret['result']: - ret['result'] = _ret['result'] - if ret['result'] is False: - return ret + + if attributes: + _ret = _attributes_present(name, attributes, region, key, keyid, profile) + ret['changes'] = dictupdate.update(ret['changes'], _ret['changes']) + ret['comment'] = ' '.join([ret['comment'], _ret['comment']]) + + if not _ret['result']: + ret['result'] = _ret['result'] + if ret['result'] is False: + return ret + _ret = _health_check_present(name, health_check, region, key, keyid, profile) ret['changes'] = dictupdate.update(ret['changes'], _ret['changes']) diff --git a/salt/states/cron.py b/salt/states/cron.py index 5c9cdfdb1fd..d194673b6cf 100644 --- a/salt/states/cron.py +++ b/salt/states/cron.py @@ -125,8 +125,7 @@ from salt.ext.six import string_types import salt.utils from salt.modules.cron import ( _needs_change, - _cron_matched, - SALT_CRON_NO_IDENTIFIER + _cron_matched ) @@ -217,7 +216,7 @@ def present(name, month='*', dayweek='*', comment=None, - identifier=None): + identifier=False): ''' Verifies that the specified cron job is present for the specified user. For more advanced information about what exactly can be set in the cron @@ -257,8 +256,8 @@ def present(name, edits. This defaults to the state id ''' name = ' '.join(name.strip().split()) - if not identifier: - identifier = SALT_CRON_NO_IDENTIFIER + if identifier is False: + identifier = name ret = {'changes': {}, 'comment': '', 'name': name, @@ -313,7 +312,7 @@ def present(name, def absent(name, user='root', - identifier=None, + identifier=False, **kwargs): ''' Verifies that the specified cron job is absent for the specified user; only @@ -335,8 +334,8 @@ def absent(name, ### of unsupported arguments will result in a traceback. name = ' '.join(name.strip().split()) - if not identifier: - identifier = SALT_CRON_NO_IDENTIFIER + if identifier is False: + identifier = name ret = {'name': name, 'result': True, 'changes': {}, diff --git a/salt/states/grains.py b/salt/states/grains.py index 76a07685a55..4ee6851a463 100644 --- a/salt/states/grains.py +++ b/salt/states/grains.py @@ -220,8 +220,7 @@ def absent(name, destructive=False): 'changes': {}, 'result': True, 'comment': ''} - grain = __grains__.get(name) - if grain: + if name in __grains__: if __opts__['test']: ret['result'] = None if destructive is True: diff --git a/salt/states/openstack_config.py b/salt/states/openstack_config.py index e9563af2800..fe95c0a207d 100644 --- a/salt/states/openstack_config.py +++ b/salt/states/openstack_config.py @@ -8,10 +8,12 @@ Manage OpenStack configuration file settings. :platform: linux ''' + +# Import Python Libs from __future__ import absolute_import -# Import salt libs -import salt.exceptions +# Import Salt Libs +from salt.exceptions import CommandExecutionError def __virtual__(): @@ -48,18 +50,30 @@ def present(name, filename, section, value, parameter=None): if parameter is None: parameter = name + ret = {'name': name, + 'changes': {}, + 'result': False, + 'comment': ''} + try: old_value = __salt__['openstack_config.get'](filename=filename, section=section, parameter=parameter) if old_value == value: - return {'name': name, - 'changes': {}, - 'result': True, - 'comment': 'The value is already set to the correct value'} + ret['result'] = True + ret['comment'] = 'The value is already set to the correct value' + return ret - except salt.exceptions.CommandExecutionError as e: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Value \'{0}\' is set to be changed to \'{1}\'.'.format( + old_value, + value + ) + return ret + + except CommandExecutionError as e: if not str(e).lower().startswith('parameter not found:'): raise @@ -68,10 +82,11 @@ def present(name, filename, section, value, parameter=None): parameter=parameter, value=value) - return {'name': name, - 'changes': {'Value': 'Updated'}, - 'result': True, - 'comment': 'The value has been updated'} + ret['changes'] = {'Value': 'Updated'} + ret['result'] = True + ret['comment'] = 'The value has been updated' + + return ret def absent(name, filename, section, parameter=None): @@ -92,23 +107,35 @@ def absent(name, filename, section, parameter=None): if parameter is None: parameter = name + ret = {'name': name, + 'changes': {}, + 'result': False, + 'comment': ''} + try: old_value = __salt__['openstack_config.get'](filename=filename, section=section, parameter=parameter) - except salt.exceptions.CommandExecutionError as e: + except CommandExecutionError as e: if str(e).lower().startswith('parameter not found:'): - return {'name': name, - 'changes': {}, - 'result': True, - 'comment': 'The value is already absent'} + ret['result'] = True + ret['comment'] = 'The value is already absent' + return ret raise + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Value \'{0}\' is set to be deleted.'.format( + old_value + ) + return ret + __salt__['openstack_config.delete'](filename=filename, section=section, parameter=parameter) - return {'name': name, - 'changes': {'Value': 'Deleted'}, - 'result': True, - 'comment': 'The value has been deleted'} + ret['changes'] = {'Value': 'Deleted'} + ret['result'] = True + ret['comment'] = 'The value has been deleted' + + return ret diff --git a/salt/states/pip_state.py b/salt/states/pip_state.py index a3f3ff18f65..a06a064dc28 100644 --- a/salt/states/pip_state.py +++ b/salt/states/pip_state.py @@ -59,7 +59,7 @@ def __virtual__(): ''' Only load if the pip module is available in __salt__ ''' - if HAS_PIP and 'pip.list' in __salt__: + if 'pip.list' in __salt__: return __virtualname__ return False @@ -100,6 +100,16 @@ def _check_pkg_version_format(pkg): ret = {'result': False, 'comment': None, 'prefix': None, 'version_spec': None} + + if not HAS_PIP: + ret['comment'] = ( + 'An importable pip module is required but could not be found on ' + 'your system. This usually means that the system''s pip package ' + 'is not installed properly.' + ) + + return ret + from_vcs = False try: # Get the requirement object from the pip library diff --git a/salt/states/rabbitmq_user.py b/salt/states/rabbitmq_user.py index 0366a3725b6..d68a52a4161 100644 --- a/salt/states/rabbitmq_user.py +++ b/salt/states/rabbitmq_user.py @@ -42,14 +42,14 @@ def __virtual__(): return salt.utils.which('rabbitmqctl') is not None -def _check_perms_changes(name, newperms): +def _check_perms_changes(name, newperms, runas=None): ''' Whether Rabbitmq user's permissions need to be changed ''' if not newperms: return False - existing_perms = __salt__['rabbitmq.list_user_permissions'](name) + existing_perms = __salt__['rabbitmq.list_user_permissions'](name, runas=runas) perm_need_change = False for vhost_perms in newperms: @@ -63,14 +63,14 @@ def _check_perms_changes(name, newperms): return perm_need_change -def _check_tags_changes(name, newtags): +def _check_tags_changes(name, newtags, 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']()[name] - set(newtags) + return __salt__['rabbitmq.list_users'](runas=runas)[name] - set(newtags) else: return [] @@ -147,7 +147,7 @@ def present(name, name, runas=runas) changes['old'] += 'Removed password.\n' - if _check_tags_changes(name, tags): + if _check_tags_changes(name, tags, runas=runas): if __opts__['test']: ret['result'] = None ret['comment'] += ('Tags for user {0} ' @@ -158,7 +158,7 @@ def present(name, ) changes['new'] += 'Set tags: {0}\n'.format(tags) - if _check_perms_changes(name, perms): + if _check_perms_changes(name, perms, runas=runas): if __opts__['test']: ret['result'] = None ret['comment'] += ('Permissions for user {0} ' @@ -167,7 +167,7 @@ def present(name, 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) + vhost, name, perm[0], perm[1], perm[2], runas=runas) ) changes['new'] += ( 'Set permissions {0} for vhost {1}' diff --git a/salt/states/reg.py b/salt/states/reg.py index bfec5a5bfb1..ccf4211bc32 100644 --- a/salt/states/reg.py +++ b/salt/states/reg.py @@ -1,11 +1,68 @@ # -*- coding: utf-8 -*- -''' -Manage the registry on Windows +r''' +=========================== +Manage the Windows registry +=========================== +Many python developers think of registry keys as if they were python keys in a +dictionary which is not the case. The windows registry is broken down into the +following components: + +----- +Hives +----- + +This is the top level of the registry. They all begin with HKEY. +- HKEY_CLASSES_ROOT (HKCR) +- HKEY_CURRENT_USER(HKCU) +- HKEY_LOCAL MACHINE (HKLM) +- HKEY_USER (HKU) +- HKEY_CURRENT_CONFIG + +---- +Keys +---- + +Hives contain keys. These are basically the folders beneath the hives. They can +contain any number of subkeys. + +----------------- +Values or Entries +----------------- + +Values or Entries are the name/data pairs beneath the keys and subkeys. All keys +have a default name/data pair. It is usually "(Default)"="(value not set)". The +actual value for the name and the date is Null. The registry editor will display +"(Default)" and "(value not set)". + +------- +Example +------- + +The following example is taken from the windows startup portion of the registry: +``` +[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run] +"RTHDVCPL"="\"C:\\Program Files\\Realtek\\Audio\\HDA\\RtkNGUI64.exe\" -s" +"NvBackend"="\"C:\\Program Files (x86)\\NVIDIA Corporation\\Update Core\\NvBackend.exe\"" +"BTMTrayAgent"="rundll32.exe \"C:\\Program Files (x86)\\Intel\\Bluetooth\\btmshellex.dll\",TrayApp" +``` +In this example these are the values for each: + +Hive: `HKEY_LOCAL_MACHINE` + +Key and subkeys: `SOFTWARE\Microsoft\Windows\CurrentVersion\Run` + +Value: + - There are 3 value names: `RTHDVCPL`, `NvBackend`, and `BTMTrayAgent` + - Each value name has a corresponding value ''' from __future__ import absolute_import +# Import python libs import logging +# Import salt libs +import salt.utils + log = logging.getLogger(__name__) @@ -23,7 +80,7 @@ def _parse_key_value(key): splt = key.split("\\") hive = splt.pop(0) vname = splt.pop(-1) - key = r'\\'.join(splt) + key = '\\'.join(splt) return hive, key, vname @@ -33,71 +90,149 @@ def _parse_key(key): ''' splt = key.split("\\") hive = splt.pop(0) - key = r'\\'.join(splt) + key = '\\'.join(splt) return hive, key -def present(name, value, vtype='REG_SZ', reflection=True): +def present(name, value=None, vname=None, vdata=None, vtype='REG_SZ', reflection=True): ''' - Set a registry value + Ensure a registry key or value is present. - Optionally set ``reflection`` to ``False`` to disable reflection. - ``reflection`` has no effect on a 32-bit OS. + :param str name: + A string value representing the full path of the key to include the + HIVE, Key, and all Subkeys. For example: - In the example below, this will prevent Windows from silently creating - the key in: - ``HKEY_CURRENT_USER\\SOFTWARE\\Wow6432Node\\Salt\\version`` + ``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 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 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. + + :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) + + :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. + :rtype: dict + + The following example will set the ``(Default)`` value for the + ``SOFTWARE\\Salt`` key in the ``HKEY_CURRENT_USER`` hive to ``0.15.3``. The + value will not be reflected in ``Wow6432Node``: Example: .. code-block:: yaml - HKEY_CURRENT_USER\\SOFTWARE\\Salt\\version: + HKEY_CURRENT_USER\\SOFTWARE\\Salt: reg.present: - - value: 0.15.3 - - vtype: REG_SZ + - vdata: 0.15.3 - reflection: False + The following example will set the value for the ``version`` entry under the + ``SOFTWARE\\Salt`` key in the ``HKEY_CURRENT_USER`` hive to ``0.15.3``. The + value will be reflected in ``Wow6432Node``: + + Example: + + .. code-block:: yaml + + HKEY_CURRENT_USER\\SOFTWARE\\Salt: + reg.present: + - vname: version + - vdata: 0.15.3 + 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 ``version`` will be created in the ``SOFTWARE\\Salt`` key in the - ``HKEY_CURRENT_USER`` hive and given the ``REG_SZ`` value of ``0.15.3``. + - ``vname`` is the value name ('version') that will be created under the key + - ``vdata`` is the data that will be assigned to 'version' ''' ret = {'name': name, 'result': True, 'changes': {}, 'comment': ''} - hive, key, vname = _parse_key_value(name) + # This is for backwards compatibility + # If 'value' is passed a value, vdata becomes value and the vname is + # obtained from the key path + if value: + hive, key, vname = _parse_key_value(name) + vdata = value + ret['comment'] = 'State file is using deprecated syntax. Please update.' + salt.utils.warn_until( + 'Boron', + 'The \'value\' argument has been deprecated. ' + 'Please use vdata instead.' + ) + else: + hive, key = _parse_key(name) # Determine what to do - if value == __salt__['reg.read_value'](hive, key, vname)['vdata']: - ret['comment'] = '{0} is already configured'.format(name) + reg_current = __salt__['reg.read_value'](hive, key, vname) + + if vdata == reg_current['vdata'] and reg_current['success']: + ret['comment'] = '{0} in {1} is already configured'.\ + format(vname if vname else '(Default)', name) return ret - else: - ret['changes'] = {'reg': 'configured to {0}'.format(value)} + + add_change = {'Key': r'{0}\{1}'.format(hive, key), + 'Entry': '{0}'.format(vname if vname else '(Default)'), + 'Value': '{0}'.format(vdata if vdata else '(Empty String)')} # Check for test option if __opts__['test']: ret['result'] = None + ret['changes'] = {'reg': {'Will add': add_change}} return ret # Configure the value - ret['result'] = __salt__['reg.set_value'](hive, key, vname, value, vtype, + ret['result'] = __salt__['reg.set_value'](hive, key, vname, vdata, vtype, reflection) - if not ret: + if not ret['result']: ret['changes'] = {} - ret['comment'] = 'could not configure the registry key' + ret['comment'] = r'Failed to add {0} to {1}\{2}'.format(name, hive, key) + else: + ret['changes'] = {'reg': {'Added': add_change}} + ret['comment'] = r'Added {0} to {1}\{2}'.format(name, hive, key) return ret -def absent(name): +def absent(name, vname=None): ''' - Remove a registry value + Ensure a registry value is removed. To remove a key use key_absent. Example: @@ -118,14 +253,89 @@ def absent(name): 'changes': {}, 'comment': ''} - hive, key, vname = _parse_key_value(name) + hive, key = _parse_key(name) # Determine what to do if not __salt__['reg.read_value'](hive, key, vname)['success']: + hive, key, vname = _parse_key_value(name) + if not __salt__['reg.read_value'](hive, key, vname)['success']: + ret['comment'] = '{0} is already absent'.format(name) + return ret + + remove_change = {'Key': r'{0}\{1}'.format(hive, key), + 'Entry': '{0}'.format(vname if vname else '(Default)')} + + # Check for test option + if __opts__['test']: + ret['result'] = None + ret['changes'] = {'reg': {'Will remove': remove_change}} + return ret + + # Delete the value + ret['result'] = __salt__['reg.delete_value'](hive, key, vname) + if not ret['result']: + ret['changes'] = {} + ret['comment'] = r'Failed to remove {0} from {1}\{2}'.format(name, hive, + key) + else: + ret['changes'] = {'reg': {'Removed': remove_change}} + ret['comment'] = r'Removed {0} from {1}\{2}'.format(name, hive, key) + + return ret + + +def key_absent(name, force=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 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:: + + '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 + ''' + ret = {'name': name, + 'result': True, + 'changes': {}, + 'comment': ''} + + hive, key = _parse_key(name) + + # Determine what to do + if not __salt__['reg.read_value'](hive, key)['success']: ret['comment'] = '{0} is already absent'.format(name) return ret - else: - ret['changes'] = {'reg': 'Removed {0}'.format(name)} + + ret['changes'] = {'reg': { + 'Removed': { + 'Key': r'{0}\{1}'.format(hive, key) + }}} # Check for test option if __opts__['test']: @@ -133,9 +343,10 @@ def absent(name): return ret # Delete the value - ret['result'] = __salt__['reg.delete_value'](hive, key, vname) - if not ret['result']: + __salt__['reg.delete_key'](hive, key, force=force) + if __salt__['reg.read_value'](hive, key)['success']: + ret['result'] = False ret['changes'] = {} - ret['comment'] = 'failed to remove registry key {0}'.format(name) + ret['comment'] = 'Failed to remove registry key {0}'.format(name) return ret diff --git a/salt/states/winrepo.py b/salt/states/winrepo.py index a0a7e6a4bf6..4d27c83400b 100644 --- a/salt/states/winrepo.py +++ b/salt/states/winrepo.py @@ -1,11 +1,6 @@ # -*- coding: utf-8 -*- ''' Manage Windows Package Repository - -.. note:: - - This state only loads on minions that have the ``roles: salt-master`` grain - set. ''' from __future__ import absolute_import @@ -21,15 +16,7 @@ import salt.config def __virtual__(): - ''' - Load this state if this is the salt-master - ''' - try: - return ('winrepo' - if 'salt-master' in __grains__.get('roles', []) - else False) - except TypeError: - return False + return 'winrepo' def genrepo(name, force=False, allow_empty=False): diff --git a/salt/utils/iam.py b/salt/utils/iam.py index 6b88ed09532..a9e6bd4cff5 100644 --- a/salt/utils/iam.py +++ b/salt/utils/iam.py @@ -68,7 +68,7 @@ def get_iam_region(version='latest', url='http://169.254.169.254', ''' Gets instance identity document and returns region ''' - instance_identity_url = '{0}/{1}/latest/dynamic/instance-identity/document'.format(url, version) + instance_identity_url = '{0}/{1}/dynamic/instance-identity/document'.format(url, version) region = None try: diff --git a/salt/utils/jinja.py b/salt/utils/jinja.py index a138179f8d1..643409c9b2c 100644 --- a/salt/utils/jinja.py +++ b/salt/utils/jinja.py @@ -273,7 +273,7 @@ class SerializerExtension(Extension, object): {%- set json_src = "{'bar': 'for real'}"|load_json %} Dude, {{ yaml_src.foo }} {{ json_src.bar }}! - will be rendered has:: + will be rendered as:: Dude, it works for real! @@ -299,7 +299,7 @@ class SerializerExtension(Extension, object): {% endload %} Dude, {{ yaml_src.foo }} {{ json_src.bar }}! - will be rendered has:: + will be rendered as:: Dude, it works for real! diff --git a/setup.py b/setup.py index b2f1dbbf603..82a29f38252 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ The setup script for salt ''' from __future__ import absolute_import - +# pylint: disable=file-perms # pylint: disable=C0111,E1101,E1103,F0401,W0611,W0201,W0232,R0201,R0902,R0903 # For Python 2.5. A no-op on 2.6 and above. @@ -18,7 +18,7 @@ import time try: from urllib2 import urlopen except ImportError: - from urllib.request import urlopen + from urllib.request import urlopen # pylint: disable=no-name-in-module from datetime import datetime # pylint: disable=E0611 import distutils.dist @@ -71,6 +71,7 @@ WITH_SETUPTOOLS = False if 'USE_SETUPTOOLS' in os.environ or 'setuptools' in sys.modules: try: from setuptools import setup + from setuptools.command.develop import develop from setuptools.command.install import install from setuptools.command.sdist import sdist from setuptools.command.egg_info import egg_info @@ -103,6 +104,7 @@ except ImportError: SALT_VERSION = os.path.join(os.path.abspath(SETUP_DIRNAME), 'salt', 'version.py') SALT_VERSION_HARDCODED = os.path.join(os.path.abspath(SETUP_DIRNAME), 'salt', '_version.py') +SALT_SYSPATHS_HARDCODED = os.path.join(os.path.abspath(SETUP_DIRNAME), 'salt', '_syspaths.py') SALT_REQS = os.path.join(os.path.abspath(SETUP_DIRNAME), 'requirements', 'base.txt') SALT_ZEROMQ_REQS = os.path.join(os.path.abspath(SETUP_DIRNAME), 'requirements', 'zeromq.txt') SALT_RAET_REQS = os.path.join(os.path.abspath(SETUP_DIRNAME), 'requirements', 'raet.txt') @@ -125,8 +127,16 @@ def _parse_requirements_file(requirements_file): line = line.strip() if not line or line.startswith(('#', '-r')): continue - if IS_WINDOWS_PLATFORM and 'libcloud' in line: - continue + if IS_WINDOWS_PLATFORM: + if 'libcloud' in line: + continue + if 'pycrypto' in line.lower(): + # On windows we install PyCrypto using python wheels + continue + if 'm2crypto' in line.lower() and __saltstack_version__.info < (2015, 8): # pylint: disable=undefined-variable + # In Windows, we're installing M2CryptoWin{32,64} which comes + # compiled + continue parsed_requirements.append(line) return parsed_requirements # <---- Helper Functions --------------------------------------------------------------------------------------------- @@ -139,10 +149,14 @@ class WriteSaltVersion(Command): user_options = [] def initialize_options(self): - pass + ''' + Abstract method that is required to be overwritten + ''' def finalize_options(self): - pass + ''' + Abstract method that is required to be overwritten + ''' def run(self): if not os.path.exists(SALT_VERSION_HARDCODED): @@ -161,10 +175,9 @@ class WriteSaltVersion(Command): # pylint: enable=E0602 -class WriteSaltSshPackaingFile(Command): +class GenerateSaltSyspaths(Command): - description = 'Write salt\'s ssh packaging file' - user_options = [] + description = 'Generate salt\'s hardcoded syspaths file' def initialize_options(self): pass @@ -172,6 +185,45 @@ class WriteSaltSshPackaingFile(Command): def finalize_options(self): pass + def run(self): + # Write the syspaths file + if getattr(self.distribution, 'salt_syspaths_hardcoded_path', None) is None: + print('This command is not meant to be called on it\'s own') + exit(1) + + # Write the system paths file + open(self.distribution.salt_syspaths_hardcoded_path, 'w').write( + INSTALL_SYSPATHS_TEMPLATE.format( + date=datetime.utcnow(), + root_dir=self.distribution.salt_root_dir, + config_dir=self.distribution.salt_config_dir, + cache_dir=self.distribution.salt_cache_dir, + sock_dir=self.distribution.salt_sock_dir, + srv_root_dir=self.distribution.salt_srv_root_dir, + base_file_roots_dir=self.distribution.salt_base_file_roots_dir, + base_pillar_roots_dir=self.distribution.salt_base_pillar_roots_dir, + base_master_roots_dir=self.distribution.salt_base_master_roots_dir, + logs_dir=self.distribution.salt_logs_dir, + pidfile_dir=self.distribution.salt_pidfile_dir, + ) + ) + + +class WriteSaltSshPackaingFile(Command): + + description = 'Write salt\'s ssh packaging file' + user_options = [] + + def initialize_options(self): + ''' + Abstract method that is required to be overwritten + ''' + + def finalize_options(self): + ''' + Abstract method that is required to be overwritten + ''' + def run(self): if not os.path.exists(PACKAGED_FOR_SALT_SSH_FILE): # Write the salt-ssh packaging file @@ -184,6 +236,198 @@ class WriteSaltSshPackaingFile(Command): # pylint: enable=E0602 +if WITH_SETUPTOOLS: + class Develop(develop): + user_options = develop.user_options + [ + ('write_salt_version', None, + 'Generate Salt\'s _version.py file which allows proper version ' + 'reporting. This defaults to False on develop/editable setups. ' + 'If WRITE_SALT_VERSION is found in the environment this flag is ' + 'switched to True.'), + ('generate_salt_syspaths', None, + 'Generate Salt\'s _syspaths.py file which allows tweaking some ' + 'common paths that salt uses. This defaults to False on ' + 'develop/editable setups. If GENERATE_SALT_SYSPATHS is found in ' + 'the environment this flag is switched to True.'), + ('mimic_salt_install', None, + 'Mimmic the install command when running the develop command. ' + 'This will generate salt\'s _version.py and _syspaths.py files. ' + 'Generate Salt\'s _syspaths.py file which allows tweaking some ' + 'This defaults to False on develop/editable setups. ' + 'If MIMIC_INSTALL is found in the environment this flag is ' + 'switched to True.') + ] + boolean_options = develop.boolean_options + [ + 'write_salt_version', + 'generate_salt_syspaths', + 'mimic_salt_install' + ] + + def initialize_options(self): + develop.initialize_options(self) + self.write_salt_version = False + self.generate_salt_syspaths = False + self.mimic_salt_install = False + + def finalize_options(self): + develop.finalize_options(self) + if 'WRITE_SALT_VERSION' in os.environ: + self.write_salt_version = True + if 'GENERATE_SALT_SYSPATHS' in os.environ: + self.generate_salt_syspaths = True + if 'MIMIC_SALT_INSTALL' in os.environ: + self.mimic_salt_install = True + + if self.mimic_salt_install: + self.write_salt_version = True + self.generate_salt_syspaths = True + + def run(self): + if IS_WINDOWS_PLATFORM: + if __saltstack_version__.info < (2015, 8): # pylint: disable=undefined-variable + # Install M2Crypto first + self.distribution.salt_installing_m2crypto_windows = True + self.run_command('install-m2crypto-windows') + self.distribution.salt_installing_m2crypto_windows = None + + # Install PyCrypto + self.distribution.salt_installing_pycrypto_windows = True + self.run_command('install-pycrypto-windows') + self.distribution.salt_installing_pycrypto_windows = None + + # Download the required DLLs + self.distribution.salt_download_windows_dlls = True + self.run_command('download-windows-dlls') + self.distribution.salt_download_windows_dlls = None + + if self.write_salt_version is True: + self.distribution.running_salt_install = True + self.distribution.salt_version_hardcoded_path = SALT_VERSION_HARDCODED + self.run_command('write_salt_version') + + if self.generate_salt_syspaths: + self.distribution.salt_syspaths_hardcoded_path = SALT_SYSPATHS_HARDCODED + self.run_command('generate_salt_syspaths') + + # Resume normal execution + develop.run(self) + + +class InstallM2CryptoWindows(Command): + + description = 'Install M2CryptoWindows' + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + if getattr(self.distribution, 'salt_installing_m2crypto_windows', None) is None: + print('This command is not meant to be called on it\'s own') + exit(1) + import platform + from pip.utils import call_subprocess + from pip.utils.logging import indent_log + platform_bits, _ = platform.architecture() + with indent_log(): + call_subprocess( + ['pip', 'install', '--egg', 'M2CryptoWin{0}'.format(platform_bits[:2])] + ) + + +class InstallPyCryptoWindowsWheel(Command): + + description = 'Install PyCrypto on Windows' + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + if getattr(self.distribution, 'salt_installing_pycrypto_windows', None) is None: + print('This command is not meant to be called on it\'s own') + exit(1) + import platform + from pip.utils import call_subprocess + from pip.utils.logging import indent_log + platform_bits, _ = platform.architecture() + call_arguments = ['pip', 'install', 'wheel'] + if platform_bits == '64bit': + call_arguments.append( + 'http://repo.saltstack.com/windows/dependencies/64/pycrypto-2.6.1-cp27-none-win_amd64.whl' + ) + else: + call_arguments.append( + 'http://repo.saltstack.com/windows/dependencies/32/pycrypto-2.6.1-cp27-none-win32.whl' + ) + with indent_log(): + call_subprocess(call_arguments) + + +class DownloadWindowsDlls(Command): + + description = 'Download required DLL\'s for windows' + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + if getattr(self.distribution, 'salt_download_windows_dlls', None) is None: + print('This command is not meant to be called on it\'s own') + exit(1) + import platform + from pip.utils.logging import indent_log + platform_bits, _ = platform.architecture() + url = 'https://repo.saltstack.com/windows/dependencies/{bits}/{fname}32.dll' + dest = os.path.join(os.path.dirname(sys.executable), '{fname}32.dll') + with indent_log(): + for fname in ('libeay', 'ssleay'): + furl = url.format(bits=platform_bits[:2], fname=fname) + fdest = dest.format(fname=fname) + if not os.path.exists(fdest): + log.info('Downloading {0}32.dll to {1} from {2}'.format(fname, fdest, furl)) + try: + import requests + from contextlib import closing + with closing(requests.get(furl, stream=True)) as req: + if req.status_code == 200: + with open(fdest, 'w') as wfh: + for chunk in req.iter_content(chunk_size=4096): + if chunk: # filter out keep-alive new chunks + wfh.write(chunk) + wfh.flush() + else: + log.error( + 'Failed to download {0}32.dll to {1} from {2}'.format( + fname, fdest, furl + ) + ) + except ImportError: + req = urlopen(furl) + + if req.getcode() == 200: + with open(fdest, 'w') as wfh: + while True: + for chunk in req.read(4096): + if not chunk: + break + wfh.write(chunk) + wfh.flush() + else: + log.error( + 'Failed to download {0}32.dll to {1} from {2}'.format( + fname, fdest, furl + ) + ) + + class Sdist(sdist): def make_release_tree(self, base_dir, files): @@ -318,7 +562,9 @@ class TestCommand(Command): self.runtests_opts = None def finalize_options(self): - pass + ''' + Abstract method that is required to be overwritten + ''' def run(self): from subprocess import Popen @@ -389,24 +635,10 @@ class Build(build): self.run_command('write_salt_version') # Write the system paths file - system_paths_file_path = os.path.join( + self.distribution.salt_syspaths_hardcoded_path = os.path.join( self.build_lib, 'salt', '_syspaths.py' ) - open(system_paths_file_path, 'w').write( - INSTALL_SYSPATHS_TEMPLATE.format( - date=datetime.utcnow(), - root_dir=self.distribution.salt_root_dir, - config_dir=self.distribution.salt_config_dir, - cache_dir=self.distribution.salt_cache_dir, - sock_dir=self.distribution.salt_sock_dir, - srv_root_dir=self.distribution.salt_srv_root_dir, - base_file_roots_dir=self.distribution.salt_base_file_roots_dir, - base_pillar_roots_dir=self.distribution.salt_base_pillar_roots_dir, - base_master_roots_dir=self.distribution.salt_base_master_roots_dir, - logs_dir=self.distribution.salt_logs_dir, - pidfile_dir=self.distribution.salt_pidfile_dir, - ) - ) + self.run_command('generate_salt_syspaths') class Install(install): @@ -499,6 +731,20 @@ class Install(install): self.distribution.salt_version_hardcoded_path = os.path.join( self.build_lib, 'salt', '_version.py' ) + if IS_WINDOWS_PLATFORM: + if __saltstack_version__.info < (2015, 8): # pylint: disable=undefined-variable + # Install M2Crypto first + self.distribution.salt_installing_m2crypto_windows = True + self.run_command('install-m2crypto-windows') + self.distribution.salt_installing_m2crypto_windows = None + # Install PyCrypto + self.distribution.salt_installing_pycrypto_windows = True + self.run_command('install-pycrypto-windows') + self.distribution.salt_installing_pycrypto_windows = None + # Download the required DLLs + self.distribution.salt_download_windows_dlls = True + self.run_command('download-windows-dlls') + self.distribution.salt_download_windows_dlls = None # Run install.run install.run(self) @@ -597,7 +843,6 @@ class SaltDistribution(distutils.dist.Distribution): self.salt_logs_dir = None self.salt_pidfile_dir = None - self.name = 'salt-ssh' if PACKAGED_FOR_SALT_SSH else 'salt' self.salt_version = __version__ # pylint: disable=undefined-variable self.description = 'Portable, distributed, remote execution and configuration management system' @@ -610,10 +855,19 @@ class SaltDistribution(distutils.dist.Distribution): 'sdist': Sdist, 'install': Install, 'write_salt_version': WriteSaltVersion, + 'generate_salt_syspaths': GenerateSaltSyspaths, 'write_salt_ssh_packaging_file': WriteSaltSshPackaingFile}) if not IS_WINDOWS_PLATFORM: self.cmdclass.update({'sdist': CloudSdist, 'install_lib': InstallLib}) + if IS_WINDOWS_PLATFORM: + self.cmdclass.update({'install-pycrypto-windows': InstallPyCryptoWindowsWheel, + 'download-windows-dlls': DownloadWindowsDlls}) + if __saltstack_version__.info < (2015, 8): # pylint: disable=undefined-variable + self.cmdclass.update({'install-m2crypto-windows': InstallM2CryptoWindows}) + + if WITH_SETUPTOOLS: + self.cmdclass.update({'develop': Develop}) self.license = 'Apache Software License 2.0' self.packages = self.discover_packages() @@ -731,6 +985,7 @@ class SaltDistribution(distutils.dist.Distribution): if IS_WINDOWS_PLATFORM: install_requires.append('WMI') + install_requires.append('pypiwin32 >= 219') if self.salt_transport == 'zeromq': install_requires += _parse_requirements_file(SALT_ZEROMQ_REQS) @@ -911,14 +1166,6 @@ class SaltDistribution(distutils.dist.Distribution): def parse_command_line(self): args = distutils.dist.Distribution.parse_command_line(self) - # Setup our property functions after class initialization and - # after parsing the command line since most are set to None - for funcname in dir(self): - if not funcname.startswith('_property_'): - continue - property_name = funcname.split('_property_', 1)[-1] - setattr(self, property_name, getattr(self, funcname)) - if not self.ssh_packaging and PACKAGED_FOR_SALT_SSH: self.ssh_packaging = 1 @@ -936,6 +1183,16 @@ class SaltDistribution(distutils.dist.Distribution): ) ) + # Setup our property functions after class initialization and + # after parsing the command line since most are set to None + # ATTENTION: This should be the last step before returning the args or + # some of the requirements won't be correctly set + for funcname in dir(self): + if not funcname.startswith('_property_'): + continue + property_name = funcname.split('_property_', 1)[-1] + setattr(self, property_name, getattr(self, funcname)) + return args # <---- Overridden Methods --------------------------------------------------------------------------------------- diff --git a/tests/unit/modules/pw_user_test.py b/tests/unit/modules/pw_user_test.py index 5eaa8ba5165..c6a500294bc 100644 --- a/tests/unit/modules/pw_user_test.py +++ b/tests/unit/modules/pw_user_test.py @@ -18,7 +18,11 @@ from salttesting.mock import ( # Import Salt Libs from salt.modules import pw_user from salt.exceptions import CommandExecutionError -import pwd +try: + import pwd + HAS_PWD = True +except ImportError: + HAS_PWD = False # Globals @@ -27,6 +31,7 @@ pw_user.__salt__ = {} pw_user.__context__ = {} +@skipIf(not HAS_PWD, 'These tests can only run on systems with the python pwd module') @skipIf(NO_MOCK, NO_MOCK_REASON) class PwUserTestCase(TestCase): ''' @@ -49,16 +54,21 @@ class PwUserTestCase(TestCase): with patch.dict(pw_user.__salt__, {'cmd.run_all': mock}): self.assertTrue(pw_user.delete('A'), 1) - @patch('salt.modules.pw_user.__context__', MagicMock(return_value='A')) def test_getent(self): ''' Test if user.getent already have a value ''' - self.assertTrue(pw_user.getent()) + mock_user = 'saltdude' - mock = MagicMock(return_value='A') - with patch.object(pw_user, 'info', mock): - self.assertEqual(pw_user.getent(True)[0], 'A') + class MockData(object): + pw_name = mock_user + + with patch('pwd.getpwall', MagicMock(return_value=[MockData()])): + with patch.dict(pw_user.__context__, {'user.getent': mock_user}): + self.assertEqual(pw_user.getent(), mock_user) + + with patch.object(pw_user, 'info', MagicMock(return_value=mock_user)): + self.assertEqual(pw_user.getent(True)[0], mock_user) def test_chuid(self): ''' @@ -291,13 +301,22 @@ class PwUserTestCase(TestCase): ''' Return a list of groups the named user belongs to ''' - self.assertEqual(pw_user.list_groups('name'), 'A') + mock_group = 'saltgroup' + + with patch('salt.utils.get_group_list', MagicMock(return_value=[mock_group])): + self.assertEqual(pw_user.list_groups('name'), [mock_group]) def test_list_users(self): ''' Return a list of all users ''' - self.assertTrue(pw_user.list_users()) + mock_user = 'saltdude' + + class MockData(object): + pw_name = mock_user + + with patch('pwd.getpwall', MagicMock(return_value=[MockData()])): + self.assertEqual(pw_user.list_users(), [mock_user]) def test_rename(self): ''' diff --git a/tests/unit/states/boto_elb_test.py b/tests/unit/states/boto_elb_test.py index 3bfc70e4850..22e028e6933 100644 --- a/tests/unit/states/boto_elb_test.py +++ b/tests/unit/states/boto_elb_test.py @@ -98,7 +98,7 @@ class BotoElbTestCase(TestCase): self.assertTrue(boto_elb.__salt__['boto_elb.exists'].called) self.assertTrue(boto_elb.__salt__['boto_elb.create'].called) self.assertTrue(boto_elb.__salt__['state.single'].called) - self.assertTrue( + self.assertFalse( boto_elb.__salt__['boto_elb.get_attributes'].called ) self.assertTrue( diff --git a/tests/unit/states/cron_test.py b/tests/unit/states/cron_test.py index d2a6d7785de..6901016e4cc 100644 --- a/tests/unit/states/cron_test.py +++ b/tests/unit/states/cron_test.py @@ -139,7 +139,8 @@ class CronTestCase(TestCase): cron.present( name='foo', hour='2', - user='root') + user='root', + identifier=None) self.assertEqual( get_crontab(), ('# Lines below here are managed by Salt, do not edit\n' @@ -147,7 +148,7 @@ class CronTestCase(TestCase): '* 2 * * * foo\n' '# SALT_CRON_IDENTIFIER:2\n' '* 2 * * * foo\n' - '* 2 * * * foo\n')) + '* 2 * * * foo')) @patch('salt.modules.cron.raw_cron', new=MagicMock(side_effect=get_crontab)) @@ -196,214 +197,107 @@ class CronTestCase(TestCase): new=MagicMock(side_effect=get_crontab)) @patch('salt.modules.cron._write_cron_lines', new=MagicMock(side_effect=write_crontab)) - def test_aissue_1072(self): + def test_multiline_comments_are_updated(self): set_crontab( '# Lines below here are managed by Salt, do not edit\n' - '# I have a multi-line comment SALT_CRON_IDENTIFIER:1\n' + '# First crontab - single line comment SALT_CRON_IDENTIFIER:1\n' '* 1 * * * foo' ) cron.present( name='foo', hour='1', - comment='1I have a multi-line comment\n2about my script here.\n', + comment='First crontab\nfirst multi-line comment\n', identifier='1', user='root') cron.present( name='foo', hour='1', - comment='3I have a multi-line comment\n3about my script here.\n', + comment='First crontab\nsecond multi-line comment\n', + identifier='1', user='root') cron.present( name='foo', hour='1', - comment='I have a multi-line comment\nabout my script here.\n', + comment='Second crontab\nmulti-line comment\n', identifier='2', user='root') self.assertEqual( get_crontab(), '# Lines below here are managed by Salt, do not edit\n' - '# 2about my script here. SALT_CRON_IDENTIFIER:1\n' + '# First crontab\n' + '# second multi-line comment SALT_CRON_IDENTIFIER:1\n' '* 1 * * * foo\n' - '# I have a multi-line comment\n' - '# about my script here. SALT_CRON_IDENTIFIER:2\n' + '# Second crontab\n' + '# multi-line comment SALT_CRON_IDENTIFIER:2\n' '* 1 * * * foo') @patch('salt.modules.cron.raw_cron', new=MagicMock(side_effect=get_crontab)) @patch('salt.modules.cron._write_cron_lines', new=MagicMock(side_effect=write_crontab)) - def test_issue_11935(self): + def test_existing_unmanaged_jobs_are_made_managed(self): set_crontab( '# Lines below here are managed by Salt, do not edit\n' - '0 2 * * * find /var/www -type f ' - '-mtime -7 -print0 | xargs -0 ' - 'clamscan -i --no-summary 2>/dev/null' + '0 2 * * * foo' ) - cmd = ( - 'find /var/www -type f -mtime -7 -print0 ' - '| xargs -0 clamscan -i --no-summary 2>/dev/null' - ) - self.assertEqual(cron._check_cron('root', cmd, hour='2', minute='0'), - 'present') - ret = cron.present(cmd, 'root', minute='0', hour='2') - self.assertEqual(ret['changes'], {}) - self.assertEqual( - ret['comment'], - 'Cron find /var/www -type f -mtime -7 -print0 ' - '| xargs -0 clamscan -i --no-summary 2>/dev/null already present') - self.assertEqual(cron._check_cron('root', cmd, hour='3', minute='0'), - 'update') - ret = cron.present(cmd, 'root', minute='0', hour='3') - self.assertEqual(ret['changes'], - {'root': 'find /var/www -type f -mtime -7 -print0 | ' - 'xargs -0 clamscan -i --no-summary 2>/dev/null'}) - self.assertEqual( - ret['comment'], - 'Cron find /var/www -type f -mtime -7 -print0 ' - '| xargs -0 clamscan -i --no-summary 2>/dev/null updated') + ret = cron._check_cron('root', 'foo', hour='2', minute='0') + self.assertEqual(ret, 'present') + ret = cron.present('foo', 'root', minute='0', hour='2') + self.assertEqual(ret['changes'], {'root': 'foo'}) + self.assertEqual(ret['comment'], 'Cron foo updated') self.assertEqual( get_crontab(), '# Lines below here are managed by Salt, do not edit\n' - '0 3 * * * find /var/www -type f -mtime -7 -print0 |' - ' xargs -0 clamscan -i --no-summary 2>/dev/null') + '# SALT_CRON_IDENTIFIER:foo\n' + '0 2 * * * foo') + ret = cron.present('foo', 'root', minute='0', hour='2') + self.assertEqual(ret['changes'], {}) + self.assertEqual(ret['comment'], 'Cron foo already present') @patch('salt.modules.cron.raw_cron', new=MagicMock(side_effect=get_crontab)) @patch('salt.modules.cron._write_cron_lines', new=MagicMock(side_effect=write_crontab)) - def test_issue_11935_with_id(self): + def test_existing_noid_jobs_are_updated_with_identifier(self): set_crontab( '# Lines below here are managed by Salt, do not edit\n' - '# SALT_CRON_IDENTIFIER:1\n' - '0 2 * * * find /var/www -type f ' - '-mtime -7 -print0 | xargs -0 ' - 'clamscan -i --no-summary 2>/dev/null' + '# SALT_CRON_IDENTIFIER:NO ID SET\n' + '1 * * * * foo' ) - cmd = ( - 'find /var/www -type f -mtime -7 -print0 ' - '| xargs -0 clamscan -i --no-summary 2>/dev/null' - ) - self.assertEqual(cron._check_cron( - 'root', cmd, hour='2', minute='0', identifier=1), 'present') - ret = cron.present(cmd, 'root', minute='0', hour='2', identifier='1') - self.assertEqual(ret['changes'], {}) - self.assertEqual( - ret['comment'], - 'Cron find /var/www -type f -mtime -7 -print0 ' - '| xargs -0 clamscan -i --no-summary 2>/dev/null already present') - self.assertEqual(cron._check_cron( - 'root', cmd, hour='3', minute='0', identifier='1'), 'update') - ret = cron.present(cmd, 'root', minute='0', hour='3', identifier='1') - self.assertEqual(ret['changes'], - {'root': 'find /var/www -type f -mtime -7 -print0 | ' - 'xargs -0 clamscan -i --no-summary 2>/dev/null'}) - self.assertEqual( - ret['comment'], - 'Cron find /var/www -type f -mtime -7 -print0 ' - '| xargs -0 clamscan -i --no-summary 2>/dev/null updated') + ret = cron._check_cron('root', 'foo', minute=1) + self.assertEqual(ret, 'present') + ret = cron.present('foo', 'root', minute=1) + self.assertEqual(ret['changes'], {'root': 'foo'}) + self.assertEqual(ret['comment'], 'Cron foo updated') self.assertEqual( get_crontab(), '# Lines below here are managed by Salt, do not edit\n' - '# SALT_CRON_IDENTIFIER:1\n' - '0 3 * * * find /var/www -type f -mtime -7 -print0 |' - ' xargs -0 clamscan -i --no-summary 2>/dev/null') + '# SALT_CRON_IDENTIFIER:foo\n' + '1 * * * * foo') @patch('salt.modules.cron.raw_cron', new=MagicMock(side_effect=get_crontab)) @patch('salt.modules.cron._write_cron_lines', new=MagicMock(side_effect=write_crontab)) - def test_issue_11935_mixed(self): + def test_existing_duplicate_unmanaged_jobs_are_merged_and_given_id(self): set_crontab( '# Lines below here are managed by Salt, do not edit\n' - '0 2 * * * find /var/www -type f ' - '-mtime -7 -print0 | xargs -0 ' - 'clamscan -i --no-summary 2>/dev/null' + '0 2 * * * foo\n' + '0 2 * * * foo' ) - cmd = ( - 'find /var/www -type f -mtime -7 -print0 ' - '| xargs -0 clamscan -i --no-summary 2>/dev/null' - ) - self.assertEqual(cron._check_cron('root', cmd, hour='2', minute='0'), - 'present') - ret = cron.present(cmd, 'root', minute='0', hour='2') + ret = cron._check_cron('root', 'foo', hour='2', minute='0') + self.assertEqual(ret, 'present') + ret = cron.present('foo', 'root', minute='0', hour='2') + self.assertEqual(ret['changes'], {'root': 'foo'}) + self.assertEqual(ret['comment'], 'Cron foo updated') + self.assertEqual( + get_crontab(), + '# Lines below here are managed by Salt, do not edit\n' + '# SALT_CRON_IDENTIFIER:foo\n' + '0 2 * * * foo') + ret = cron.present('foo', 'root', minute='0', hour='2') self.assertEqual(ret['changes'], {}) - self.assertEqual( - ret['comment'], - 'Cron find /var/www -type f -mtime -7 -print0 ' - '| xargs -0 clamscan -i --no-summary 2>/dev/null already present') - self.assertEqual(cron._check_cron('root', cmd, hour='3', minute='0'), - 'update') - ret = cron.present(cmd, 'root', minute='0', hour='3') - self.assertEqual(ret['changes'], - {'root': 'find /var/www -type f -mtime -7 -print0 | ' - 'xargs -0 clamscan -i --no-summary 2>/dev/null'}) - self.assertEqual( - ret['comment'], - 'Cron find /var/www -type f -mtime -7 -print0 ' - '| xargs -0 clamscan -i --no-summary 2>/dev/null updated') - self.assertEqual( - get_crontab(), - '# Lines below here are managed by Salt, do not edit\n' - '0 3 * * * find /var/www -type f -mtime -7 -print0 |' - ' xargs -0 clamscan -i --no-summary 2>/dev/null') - self.assertEqual(cron._check_cron( - 'root', cmd, hour='2', minute='0', identifier='1'), 'update') - ret = cron.present(cmd, 'root', minute='0', hour='2', identifier='1') - self.assertEqual( - ret['changes'], - {'root': 'find /var/www -type f -mtime -7 -print0 | ' - 'xargs -0 clamscan -i --no-summary 2>/dev/null'}) - self.assertEqual( - ret['comment'], - 'Cron find /var/www -type f -mtime -7 -print0 ' - '| xargs -0 clamscan -i --no-summary 2>/dev/null updated') - self.assertEqual(cron._check_cron( - 'root', cmd, hour='3', minute='0', identifier='1'), 'update') - ret = cron.present(cmd, 'root', minute='0', hour='3', identifier='1') - self.assertEqual(ret['changes'], - {'root': 'find /var/www -type f -mtime -7 -print0 | ' - 'xargs -0 clamscan -i --no-summary 2>/dev/null'}) - self.assertEqual( - ret['comment'], - 'Cron find /var/www -type f -mtime -7 -print0 ' - '| xargs -0 clamscan -i --no-summary 2>/dev/null updated') - self.assertEqual( - get_crontab(), - '# Lines below here are managed by Salt, do not edit\n' - '# SALT_CRON_IDENTIFIER:1\n' - '0 3 * * * find /var/www -type f -mtime -7 -print0 |' - ' xargs -0 clamscan -i --no-summary 2>/dev/null') - - set_crontab( - '# Lines below here are managed by Salt, do not edit\n' - '0 2 * * * find /var/www -type f ' - '-mtime -7 -print0 | xargs -0 ' - 'clamscan -i --no-summary 2>/dev/null' - ) - self.assertEqual(cron._check_cron( - 'root', cmd + "a", hour='2', minute='0', identifier='1'), 'absent') - ret = cron.present( - cmd + "a", 'root', minute='0', hour='2', identifier='1') - self.assertEqual( - ret['changes'], - {'root': 'find /var/www -type f -mtime -7 -print0 | ' - 'xargs -0 clamscan -i --no-summary 2>/dev/nulla'}) - self.assertEqual( - ret['comment'], - 'Cron find /var/www -type f -mtime -7 -print0 | ' - 'xargs -0 clamscan -i --no-summary 2>/dev/nulla added ' - 'to root\'s crontab') - self.assertEqual( - get_crontab(), - '# Lines below here are managed by Salt, do not edit\n' - '0 2 * * *' - ' find /var/www -type f -mtime -7 -print0' - ' | xargs -0 clamscan -i --no-summary 2>/dev/null\n' - '# SALT_CRON_IDENTIFIER:1\n' - '0 2 * * *' - ' find /var/www -type f -mtime -7 -print0' - ' | xargs -0 clamscan -i --no-summary 2>/dev/nulla') - + self.assertEqual(ret['comment'], 'Cron foo already present') if __name__ == '__main__': from integration import run_tests diff --git a/tests/unit/states/openstack_config_test.py b/tests/unit/states/openstack_config_test.py index 73b49445424..bfc8b4c109b 100644 --- a/tests/unit/states/openstack_config_test.py +++ b/tests/unit/states/openstack_config_test.py @@ -22,7 +22,7 @@ ensure_in_syspath('../../') from salt.states import openstack_config openstack_config.__salt__ = {} -openstack_config.__opts__ = {} +openstack_config.__opts__ = {'test': False} @skipIf(NO_MOCK, NO_MOCK_REASON) diff --git a/tests/unit/states/reg_test.py b/tests/unit/states/reg_test.py index dd849f43430..13d2f295b5b 100644 --- a/tests/unit/states/reg_test.py +++ b/tests/unit/states/reg_test.py @@ -36,28 +36,43 @@ class RegTestCase(TestCase): ''' Test to set a registry entry. ''' - name = 'HKEY_CURRENT_USER\\SOFTWARE\\Salt\\version' - value = '0.15.3' + name = 'HKEY_CURRENT_USER\\SOFTWARE\\Salt' + vname = 'version' + vdata = '0.15.3' ret = {'name': name, 'changes': {}, 'result': True, - 'comment': '{0} is already configured'.format(name)} + 'comment': '{0} in {1} is already configured'.format(vname, name)} - mock = MagicMock(side_effect=[{'vdata': value}, {'vdata': 'a'}, {'vdata': 'a'}]) + mock_read = MagicMock(side_effect=[{'vdata': vdata, 'success': True}, + {'vdata': 'a', 'success': True}, + {'vdata': 'a', 'success': True}]) mock_t = MagicMock(return_value=True) - with patch.dict(reg.__salt__, {'reg.read_value': mock, + with patch.dict(reg.__salt__, {'reg.read_value': mock_read, 'reg.set_value': mock_t}): - self.assertDictEqual(reg.present(name, value), ret) + self.assertDictEqual(reg.present(name, + vname=vname, + vdata=vdata), ret) with patch.dict(reg.__opts__, {'test': True}): ret.update({'comment': '', 'result': None, - 'changes': {'reg': 'configured to 0.15.3'}}) - self.assertDictEqual(reg.present(name, value), ret) + 'changes': {'reg': {'Will add': {'Key': name, + 'Entry': vname, + 'Value': vdata}}}}) + self.assertDictEqual(reg.present(name, + vname=vname, + vdata=vdata), ret) with patch.dict(reg.__opts__, {'test': False}): - ret.update({'result': True}) - self.assertDictEqual(reg.present(name, value), ret) + ret.update({'comment': 'Added {0} to {0}'.format(name), + 'result': True, + 'changes': {'reg': {'Added': {'Key': name, + 'Entry': vname, + 'Value': vdata}}}}) + self.assertDictEqual(reg.present(name, + vname=vname, + vdata=vdata), ret) # 'absent' function tests: 1 @@ -65,27 +80,35 @@ class RegTestCase(TestCase): ''' Test to remove a registry entry. ''' - name = 'HKEY_CURRENT_USER\\SOFTWARE\\Salt\\version' + name = 'HKEY_CURRENT_USER\\SOFTWARE\\Salt' + vname = 'version' ret = {'name': name, 'changes': {}, 'result': True, 'comment': '{0} is already absent'.format(name)} - mock = MagicMock(side_effect=[{'success': False}, {'success': True}, {'success': True}]) + mock_read = MagicMock(side_effect=[{'success': False}, + {'success': False}, + {'success': True}, + {'success': True}]) mock_t = MagicMock(return_value=True) - with patch.dict(reg.__salt__, {'reg.read_value': mock, + with patch.dict(reg.__salt__, {'reg.read_value': mock_read, 'reg.delete_value': mock_t}): - self.assertDictEqual(reg.absent(name), ret) + self.assertDictEqual(reg.absent(name, vname), ret) with patch.dict(reg.__opts__, {'test': True}): ret.update({'comment': '', 'result': None, - 'changes': {'reg': 'Removed {0}'.format(name)}}) - self.assertDictEqual(reg.absent(name), ret) + 'changes': {'reg': {'Will remove': {'Entry': vname, + 'Key': name}}}}) + self.assertDictEqual(reg.absent(name, vname), ret) with patch.dict(reg.__opts__, {'test': False}): - ret.update({'result': True}) - self.assertDictEqual(reg.absent(name), ret) + ret.update({'result': True, + 'changes': {'reg': {'Removed': {'Entry': vname, + 'Key': name}}}, + 'comment': 'Removed {0} from {0}'.format(name)}) + self.assertDictEqual(reg.absent(name, vname), ret) if __name__ == '__main__':