Merge pull request #30800 from twangboy/chrome

Ability to handle special case installations
This commit is contained in:
Mike Place 2016-02-02 07:25:44 -07:00
commit 8abb5b30ad
3 changed files with 164 additions and 20 deletions

View file

@ -180,7 +180,13 @@ copy of the repository cache. Command examples are as follows:
Creating a Package Definition SLS File
======================================
The package definition file should look similar to this example for Firefox:
The package definition file is a yaml file that contains all the information
needed to install a piece of software using salt. It defines information about
the package to include version, full name, flags required for the installer and
uninstaller, whether or not to use the windows task scheduler to install the
package, where to find the installation package, etc.
Take a look at this example for Firefox:
.. code-block:: yaml
@ -210,12 +216,48 @@ The package definition file should look similar to this example for Firefox:
uninstaller: '%ProgramFiles(x86)%/Mozilla Firefox/uninstall/helper.exe'
uninstall_flags: '/S'
More examples can be found at https://github.com/saltstack/salt-winrepo-ng
Each software definition file begins with a package name for the software. As in
the example above ``firefox``. The next line is indented two spaces and contains
the version to be defined. As in the example above, a software definition file
can define multiple versions for the same piece of software. The lines following
the version are indented two more spaces and contain all the information needed
to install that package.
The version number and ``full_name`` need to match the output from ``pkg.list_pkgs``
so that the status can be verified when running highstate.
Note: It is still possible to successfully install packages using ``pkg.install``
even if they don't match which can make this hard to troubleshoot.
.. warning:: The package name and the ``full_name`` must be unique to all
other packages in the software repository.
The version line is the version for the package to be installed. It is used when
you need to install a specific version of a piece of software.
.. warning:: The version must be enclosed in quotes, otherwise the yaml parser
will remove trailing zeros.
.. note:: There are unique situations where previous versions are unavailable.
Take Google Chrome for example. There is only one url provided for a standalone
installation of Google Chrome. (https://dl.google.com/edgedl/chrome/install/GoogleChromeStandaloneEnterprise.msi)
When a new version is released, the url just points to the new version. To handle
situations such as these, set the version to `latest`. Salt will install the
version of chrome at the URL and report that version. Here's an example:
.. code-block:: bash
chrome:
latest:
full_name: 'Google Chrome'
installer: 'https://dl.google.com/edgedl/chrome/install/GoogleChromeStandaloneEnterprise.msi'
install_flags: '/qn /norestart'
uninstaller: 'https://dl.google.com/edgedl/chrome/install/GoogleChromeStandaloneEnterprise.msi'
uninstall_flags: '/qn /norestart'
msiexec: True
locale: en_US
reboot: False
Available parameters are as follows:
:param str full_name: The Full Name for the software as shown in "Programs and
Features" in the control panel. You can also get this information by installing
the package manually and then running ``pkg.list_pkgs``. Here's an example of
the output from ``pkg.list_pkgs``:
.. code-block:: bash
@ -241,9 +283,13 @@ even if they don't match which can make this hard to troubleshoot.
Salt Minion 0.16.0:
0.16.0
If any of these preinstalled packages already exist in winrepo the full_name
will be automatically renamed to their package name during the next update
(running highstate or installing another package).
Notice the Full Name for Firefox: Mozilla Firefox 17.0.0 (x86 en-US). That's
exactly what's in the ``full_name`` parameter in the software definition file.
If any of the software insalled on the machine matches one of the software
definition files in the repository the full_name will be automatically renamed
to the package name. The example below shows the ``pkg.list_pkgs`` for a
machine that already has Mozilla Firefox 17.0.1 installed.
.. code-block:: bash
@ -268,15 +314,46 @@ will be automatically renamed to their package name during the next update
nsclient:
0.3.9.328
Add ``msiexec: True`` if using an MSI installer requiring the use of ``msiexec
/i`` to install and ``msiexec /x`` to uninstall.
.. important:: The version number and ``full_name`` need to match the output
from ``pkg.list_pkgs`` so that the status can be verified when running
highstate.
The ``install_flags`` and ``uninstall_flags`` are flags passed to the software
installer to cause it to perform a silent install. These can often be found by
adding ``/?`` or ``/h`` when running the installer from the command line. A
great resource for finding these silent install flags can be found on the WPKG
.. note:: It is still possible to successfully install packages using
``pkg.install`` even if they don't match. This can make troubleshooting
difficult so be careful.
:param str installer: The path to the ``.exe`` or ``.msi`` to use to install the
package. This can be a path or a URL. If it is a URL or a salt path (salt://),
the package will be cached locally and then executed. If it is a path to a file
on disk or a file share, it will be executed directly.
:param str install_flags: Any flags that need to be passed to the installer to
make it perform a silent install. These can often be found by adding ``/?`` or
``/h`` when running the installer from the command line. A great resource for
finding these silent install flags can be found on the WPKG project's wiki_:
Salt will not return if the installer is waiting for user input so these are
important.
:param str uninstaller: The path to the program used to uninstall this software.
This can be the path to the same `exe` or `msi` used to install the software. It
can also be a GUID. You can find this value in the registry under the following
keys:
- Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall
- Software\\Wow6432None\\Microsoft\\Windows\\CurrentVersion\\Uninstall
:param str uninstall_flags: Any flags that need to be passed to the uninstaller
to make it perform a silent uninstall. These can often be found by adding
``/?`` or ``/h`` when running the uninstaller from the command line. A great
resource for finding these silent install flags can be found on the WPKG
project's wiki_:
Salt will not return if the uninstaller is waiting for user input so these are
important.
Here are some examples of installer and uninstaller settings:
.. code-block:: yaml
7zip:
@ -303,9 +380,19 @@ Alternatively the ``uninstaller`` can also simply repeat the URL of the msi file
uninstaller: salt://win/repo/7zip/7z920-x64.msi
uninstall_flags: '/qn /norestart'
Add ``cache_dir: True`` when the installer requires multiple source files. The
directory containing the installer file will be recursively cached on the minion.
Only applies to salt: installer URLs.
:param bool msiexec: This tells salt to use ``msiexec /i`` to install the
package and ``msiexec /x`` to uninstall. This is for `.msi` installations.
:param bool allusers: This parameter is specific to `.msi` installations. It
tells `msiexec` to install the software for all users. The default is True.
:param bool cache_dir: If true, the entire directory where the installer resides
will be recursively cached. This is useful for installers that depend on other
files in the same directory for installation.
.. note:: Only applies to salt: installer URLs.
Here's an example for a software package that has dependent files:
.. code-block:: yaml
@ -317,6 +404,16 @@ Only applies to salt: installer URLs.
install_flags: '/ACTION=install /IACCEPTSQLSERVERLICENSETERMS /Q'
cache_dir: True
:param bool use_scheduler: If true, windows will use the task scheduler to run
the installation. This is useful for running the salt installation itself as
the installation process kills any currently running instances of salt.
:param bool reboot: Not implemented
:param str local: Not implemented
Examples can be found at https://github.com/saltstack/salt-winrepo-ng
.. _standalone-winrepo:

View file

@ -39,6 +39,8 @@ import logging
# Import third party libs
try:
from salt.ext.six.moves import winreg as _winreg # pylint: disable=import-error,no-name-in-module
from win32gui import SendMessageTimeout
from win32con import HWND_BROADCAST, WM_SETTINGCHANGE, SMTO_ABORTIFHUNG
HAS_WINDOWS_MODULES = True
except ImportError:
HAS_WINDOWS_MODULES = False
@ -129,6 +131,17 @@ def _key_exists(hive, key, use_32bit_registry=False):
return False
def broadcast_change():
'''
Refresh the windows environment.
:return:
'''
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms644952(v=vs.85).aspx
_, res = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, 0,
SMTO_ABORTIFHUNG, 5000)
return not bool(res)
def read_key(hkey, path, key=None, use_32bit_registry=False):
'''
.. important::
@ -363,6 +376,7 @@ def set_value(hive,
_winreg.SetValueEx(handle, vname, 0, vtype, vdata)
_winreg.FlushKey(handle)
_winreg.CloseKey(handle)
broadcast_change()
return True
except (WindowsError, ValueError, TypeError) as exc: # pylint: disable=E0602
log.error(exc, exc_info=True)
@ -496,6 +510,7 @@ def delete_key(hkey,
key_handle = _winreg.OpenKey(hive, key, 0, access_mask)
_winreg.DeleteKey(key_handle, '')
_winreg.CloseKey(key_handle)
broadcast_change()
return True
except WindowsError as exc: # pylint: disable=E0602
log.error(exc, exc_info=True)
@ -576,6 +591,8 @@ def delete_key_recursive(hive, key, use_32bit_registry=False):
log.error(exc, exc_info=True)
ret['Failed'].append(r'{0}\{1} {2}'.format(hive, sub_key_path, exc))
broadcast_change()
return ret
@ -620,6 +637,7 @@ def delete_value(hive, key, vname=None, reflection=True, use_32bit_registry=Fals
handle = _winreg.OpenKey(hive, key, 0, access_mask)
_winreg.DeleteValue(handle, vname)
_winreg.CloseKey(handle)
broadcast_change()
return True
except WindowsError as exc: # pylint: disable=E0602
log.error(exc, exc_info=True)

View file

@ -546,6 +546,7 @@ def install(name=None, refresh=False, pkgs=None, saltenv='base', **kwargs):
# Loop through each package
changed = []
latest = []
for pkg_name, options in six.iteritems(pkg_params):
# Load package information for the package
@ -579,6 +580,9 @@ def install(name=None, refresh=False, pkgs=None, saltenv='base', **kwargs):
ret[pkg_name] = {'not found': version_num}
continue
if 'latest' in pkginfo:
latest.append(pkg_name)
# Get the installer
installer = pkginfo[version_num].get('installer')
@ -694,16 +698,37 @@ def install(name=None, refresh=False, pkgs=None, saltenv='base', **kwargs):
# Get a new list of installed software
new = list_pkgs()
# For installers that have no specific version (ie: chrome)
# The software definition file will have a version of 'latest'
# In that case there's no way to know which version has been installed
# Just return the current installed version
# This has to be done before the loop below, otherwise the installation
# will not be detected
if latest:
for pkg_name in latest:
if old.get(pkg_name, 'old') == new.get(pkg_name, 'new'):
ret[pkg_name] = {'current': new[pkg_name]}
# Sometimes the installer takes awhile to update the registry
# This checks 10 times, 3 seconds between each for a registry change
tries = 0
difference = salt.utils.compare_dicts(old, new)
while not all(name in difference for name in changed) and tries < 10:
__salt__['reg.broadcast_change']()
time.sleep(3)
new = list_pkgs()
difference = salt.utils.compare_dicts(old, new)
tries += 1
log.debug("Try {0}".format(tries))
if tries == 10:
ret['_comment'] = 'Registry not updated.'
if not latest:
ret['_comment'] = 'Software not found in the registry.\n' \
'Could be a problem with the Software\n' \
'definition file. Verify the full_name\n' \
'and the version match the registry ' \
'exactly.\n' \
'Failed after {0} tries.'.format(tries)
# Compare the software list before and after
# Add the difference to ret
@ -811,6 +836,9 @@ def remove(name=None, pkgs=None, version=None, **kwargs):
else:
version_num = version
if 'latest' in pkginfo and version_num not in pkginfo:
version_num = 'latest'
# Check to see if package is installed on the system
if target not in old:
log.error('{0} {1} not installed'.format(target, version))
@ -818,7 +846,8 @@ def remove(name=None, pkgs=None, version=None, **kwargs):
continue
else:
if not version_num == old.get(target) \
and not old.get(target) == "Not Found":
and not old.get(target) == "Not Found" \
and not version_num == 'latest':
log.error('{0} {1} not installed'.format(target, version))
ret[target] = {'current': '{0} not installed'.format(version_num)}
continue