saltstack/salt

View on GitHub
salt/states/macpackage.py

Summary

Maintainability
D
1 day
Test Coverage
# -*- coding: utf-8 -*-
'''
Installing of mac pkg files
===========================

Install any kind of pkg, dmg or app file on macOS:

.. code-block:: yaml

    /mnt/test.pkg:
      macpackage.installed:
        - store: True

    /mnt/test.dmg:
      macpackage.installed:
        - dmg: True

    /mnt/xcode.dmg:
      macpackage.installed:
        - dmg: True
        - app: True
        - target: /Applications/Xcode.app
        - version_check: xcodebuild -version=Xcode 7.1\\n.*7B91b

'''
# Import Python libs
from __future__ import absolute_import, unicode_literals, print_function
import logging
import os
import re

# Import Salt libs
import salt.utils.platform
from salt.exceptions import CommandExecutionError

log = logging.getLogger(__name__)
__virtualname__ = "macpackage"


def __virtual__():
    '''
    Only work on Mac OS
    '''
    if salt.utils.platform.is_darwin():
        return __virtualname__
    return False


def installed(name, target="LocalSystem", dmg=False, store=False, app=False, mpkg=False, user=None, onlyif=None,
              unless=None, force=False, allow_untrusted=False, version_check=None):
    '''
    Install a Mac OS Package from a pkg or dmg file, if given a dmg file it
    will first be mounted in a temporary location

    name
        The pkg or dmg file to install

    target
        The location in which to install the package. This can be a path or LocalSystem

    dmg
        Is the given file a dmg file?

    store
        Should the pkg be installed as if it was from the Mac OS Store?

    app
        Is the file a .app? If so then we'll just copy that to /Applications/ or the given
        target

    mpkg
        Is the file a .mpkg? If so then we'll check all of the .pkg files found are installed

    user
        Name of the user performing the unless or onlyif checks

    onlyif
        A command to run as a check, run the named command only if the command
        passed to the ``onlyif`` option returns true

    unless
        A command to run as a check, only run the named command if the command
        passed to the ``unless`` option returns false

    force
        Force the package to be installed even if its already been found installed

    allow_untrusted
        Allow the installation of untrusted packages

    version_check
        The command and version that we want to check against, the version number can use regex.

        .. code-block:: yaml

            version_check: python --version_check=2.7.[0-9]

    '''
    ret = {'name': name,
           'result': True,
           'comment': '',
           'changes': {}}
    found = []
    installing = []

    real_pkg = name

    # Check onlyif, unless first
    run_check_cmd_kwargs = {'runas': user, 'python_shell': True}
    if 'shell' in __grains__:
        run_check_cmd_kwargs['shell'] = __grains__['shell']

    cret = _mod_run_check(run_check_cmd_kwargs, onlyif, unless)

    if isinstance(cret, dict):
        ret.update(cret)
        return ret

    # Check version info
    if version_check is not None:
        split = version_check.split("=")
        if len(split) == 2:
            version_cmd = split[0]
            expected_version = split[1]
            try:
                version_out = __salt__['cmd.run'](version_cmd, output_loglevel="quiet", ignore_retcode=True)
                version_out = version_out.strip()
            except CommandExecutionError:
                version_out = ""

            if re.match(expected_version, version_out) is not None:
                ret['comment'] += "Version already matches {0}".format(expected_version)
                return ret
            else:
                ret['comment'] += "Version {0} doesn't match {1}. ".format(version_out, expected_version)

    if app and target == "LocalSystem":
        target = "/Applications/"

    # Mount the dmg first
    mount_point = None
    if dmg:
        out, mount_point = __salt__['macpackage.mount'](name)
        if 'attach failed' in out:
            ret['result'] = False
            ret['comment'] += 'Unable to mount {0}'.format(name)
            return ret

        if app:
            real_pkg = mount_point + "/*.app"
        elif mpkg:
            real_pkg = mount_point + "/*.mpkg"
        else:
            real_pkg = mount_point + "/*.pkg"

    try:
        # Check if we have already installed this
        if app:
            if dmg:
                # Run with python shell due to the wildcard
                cmd = 'ls -d *.app'
                out = __salt__['cmd.run'](cmd, cwd=mount_point, python_shell=True)

                if '.app' not in out:
                    ret['result'] = False
                    ret['comment'] += 'Unable to find .app in {0}'.format(mount_point)
                    return ret
                else:
                    pkg_ids = out.split("\n")
            else:
                pkg_ids = [os.path.basename(name)]
                mount_point = os.path.dirname(name)

            if onlyif is None and unless is None and version_check is None:
                for p in pkg_ids:
                    if target[-4:] == ".app":
                        install_dir = target
                    else:
                        install_dir = os.path.join(target, p)
                    if os.path.exists(install_dir) and force is False:
                        found.append(p)
                    else:
                        installing.append(p)
            else:
                installing = pkg_ids
        else:
            installed_pkgs = __salt__['macpackage.installed_pkgs']()

            if mpkg:
                pkg_ids = __salt__['macpackage.get_mpkg_ids'](real_pkg)
            else:
                pkg_ids = __salt__['macpackage.get_pkg_id'](real_pkg)

            if pkg_ids:
                for p in pkg_ids:
                    if p in installed_pkgs and force is False:
                        found.append(p)
                    else:
                        installing.append(p)
                if len(pkg_ids) == len(found):
                    return ret

        if app:
            def failed_pkg(f_pkg):
                ret['result'] = False
                ret['comment'] += '{0} failed to install: {1}'.format(name, out)

                if 'failed' in ret['changes']:
                    ret['changes']['failed'].append(f_pkg)
                else:
                    ret['changes']['failed'] = [f_pkg]

            for app in installing:
                try:
                    log.info('Copying %s to %s', app, target)

                    out = __salt__['macpackage.install_app'](os.path.join(mount_point, app), target)

                    if out:
                        failed_pkg(app)
                    else:
                        ret['comment'] += '{0} installed'.format(app)
                        if 'installed' in ret['changes']:
                            ret['changes']['installed'].append(app)
                        else:
                            ret['changes']['installed'] = [app]

                except OSError:
                    failed_pkg(app)
        else:
            out = __salt__['macpackage.install'](real_pkg, target, store, allow_untrusted)

            if out['retcode'] != 0:
                ret['result'] = False
                ret['comment'] += '. {0} failed to install: {1}'.format(name, out)
            else:
                ret['comment'] += '{0} installed'.format(name)
                ret['changes']['installed'] = installing

    finally:
        if dmg:
            # Unmount to be kind
            __salt__['macpackage.unmount'](mount_point)

    return ret


def _mod_run_check(cmd_kwargs, onlyif, unless):
    '''
    Execute the onlyif and unless logic.
    Return a result dict if:
    * onlyif failed (onlyif != 0)
    * unless succeeded (unless == 0)
    else return True
    '''
    if onlyif:
        if __salt__['cmd.retcode'](onlyif, **cmd_kwargs) != 0:
            return {'comment': 'onlyif condition is false',
                    'skip_watch': True,
                    'result': True}

    if unless:
        if __salt__['cmd.retcode'](unless, **cmd_kwargs) == 0:
            return {'comment': 'unless condition is true',
                    'skip_watch': True,
                    'result': True}

    # No reason to stop, return True
    return True