mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #30800 from twangboy/chrome
Ability to handle special case installations
This commit is contained in:
commit
8abb5b30ad
3 changed files with 164 additions and 20 deletions
|
@ -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:
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue