From 02694b80de74c5d0c4558ffa432270e66292481c Mon Sep 17 00:00:00 2001 From: Daniel Jaouen Date: Wed, 19 Feb 2014 14:46:44 -0500 Subject: [PATCH 1/7] Update homebrew module. --- library/packaging/homebrew | 814 ++++++++++++++++++++++++++++++++----- 1 file changed, 710 insertions(+), 104 deletions(-) diff --git a/library/packaging/homebrew b/library/packaging/homebrew index ab1362acf1d..feea2dc8e03 100644 --- a/library/packaging/homebrew +++ b/library/packaging/homebrew @@ -2,6 +2,8 @@ # -*- coding: utf-8 -*- # (c) 2013, Andrew Dunham +# (c) 2013, Daniel Jaouen +# # Based on macports (Jimmy Tang ) # # This module is free software: you can redistribute it and/or modify @@ -24,7 +26,7 @@ author: Andrew Dunham short_description: Package manager for Homebrew description: - Manages Homebrew packages -version_added: "1.4" +version_added: "1.1" options: name: description: @@ -33,7 +35,7 @@ options: state: description: - state of the package - choices: [ 'present', 'absent' ] + choices: [ 'head', 'latest', 'installed', 'linked', 'uninstalled' ] required: false default: present update_homebrew: @@ -42,135 +44,739 @@ options: required: false default: "no" choices: [ "yes", "no" ] - install_options: - description: - - options flags to install a package - required: false - default: null notes: [] ''' EXAMPLES = ''' - homebrew: name=foo state=present - homebrew: name=foo state=present update_homebrew=yes +- homebrew: name=foo state=latest update_homebrew=yes +- homebrew: update_homebrew=yes upgrade=yes +- homebrew: name=foo state=head +- homebrew: name=foo state=linked - homebrew: name=foo state=absent - homebrew: name=foo,bar state=absent -- homebrew: name=foo state=present install_options=with-baz,enable-debug ''' - -def update_homebrew(module, brew_path): - """ Updates packages list. """ - - rc, out, err = module.run_command("%s update" % brew_path) - - if rc != 0: - module.fail_json(msg="could not update homebrew") +import os.path +import re -def query_package(module, brew_path, name, state="present"): - """ Returns whether a package is installed or not. """ +# exceptions -------------------------------------------------------------- {{{ +class HomebrewException(Exception): + pass +# /exceptions ------------------------------------------------------------- }}} - if state == "present": - rc, out, err = module.run_command("%s list %s" % (brew_path, name)) - if rc == 0: + +# utils ------------------------------------------------------------------- {{{ +def _create_regex_group(s): + lines = (line.strip() for line in s.split('\n') if line.strip()) + chars = filter(None, (line.split('#')[0].strip() for line in lines)) + group = r'[^' + r''.join(chars) + r']' + return re.compile(group) +# /utils ------------------------------------------------------------------ }}} + + +class Homebrew(object): + '''A class to manage Homebrew packages.''' + + # class regexes ------------------------------------------------ {{{ + VALID_PATH_CHARS = r''' + \w # alphanumeric characters (i.e., [a-zA-Z0-9_]) + \s # spaces + : # colons + {sep} # the OS-specific path separator + - # dashes + '''.format(sep=os.path.sep) + + VALID_BREW_PATH_CHARS = r''' + \w # alphanumeric characters (i.e., [a-zA-Z0-9_]) + \s # spaces + {sep} # the OS-specific path separator + - # dashes + '''.format(sep=os.path.sep) + + VALID_PACKAGE_CHARS = r''' + \w # alphanumeric characters (i.e., [a-zA-Z0-9_]) + - # dashes + ''' + + INVALID_PATH_REGEX = _create_regex_group(VALID_PATH_CHARS) + INVALID_BREW_PATH_REGEX = _create_regex_group(VALID_BREW_PATH_CHARS) + INVALID_PACKAGE_REGEX = _create_regex_group(VALID_PACKAGE_CHARS) + # /class regexes ----------------------------------------------- }}} + + # class validations -------------------------------------------- {{{ + @classmethod + def valid_path(cls, path): + ''' + `path` must be one of: + - list of paths + - a string containing only: + - alphanumeric characters + - dashes + - spaces + - colons + - os.path.sep + ''' + + if isinstance(path, basestring): + return not cls.INVALID_PATH_REGEX.search(path) + + try: + iter(path) + except TypeError: + return False + else: + paths = path + return all(cls.valid_brew_path(path_) for path_ in paths) + + @classmethod + def valid_brew_path(cls, brew_path): + ''' + `brew_path` must be one of: + - None + - a string containing only: + - alphanumeric characters + - dashes + - spaces + - os.path.sep + ''' + + if brew_path is None: return True - return False + return ( + isinstance(brew_path, basestring) + and not cls.INVALID_BREW_PATH_REGEX.search(brew_path) + ) + + @classmethod + def valid_package(cls, package): + '''A valid package is either None or alphanumeric.''' + + if package is None: + return True + + return ( + isinstance(package, basestring) + and not cls.INVALID_PACKAGE_REGEX.search(package) + ) + + @classmethod + def valid_state(cls, state): + ''' + A valid state is one of: + - None + - installed + - upgraded + - head + - linked + - absent + ''' + + if state is None: + return True + else: + return ( + isinstance(state, basestring) + and state.lower() in ( + 'installed', + 'upgraded', + 'head', + 'linked', + 'absent', + ) + ) + + @classmethod + def valid_module(cls, module): + '''A valid module is an instance of AnsibleModule.''' + + return isinstance(module, AnsibleModule) + + # /class validations ------------------------------------------- }}} + + # class properties --------------------------------------------- {{{ + @property + def module(self): + return self._module + + @module.setter + def module(self, module): + if not self.valid_module(module): + self._module = None + self.failed = True + self.message = 'Invalid module: {0}.'.format(module) + raise HomebrewException(self.message) + + else: + self._module = module + return module + + @property + def path(self): + return self._path + + @path.setter + def path(self, path): + if not self.valid_path(path): + self._path = [] + self.failed = True + self.message = 'Invalid path: {0}.'.format(path) + raise HomebrewException(self.message) + + else: + if isinstance(path, basestring): + self._path = path.split(':') + else: + self._path = path + + return path + + @property + def brew_path(self): + return self._brew_path + + @brew_path.setter + def brew_path(self, brew_path): + if not self.valid_brew_path(brew_path): + self._brew_path = None + self.failed = True + self.message = 'Invalid brew_path: {0}.'.format(brew_path) + raise HomebrewException(self.message) + + else: + self._brew_path = brew_path + return brew_path + + @property + def params(self): + return self._params + + @params.setter + def params(self, params): + self._params = self.module.params + return self._params + + @property + def current_package(self): + return self._current_package + + @current_package.setter + def current_package(self, package): + if not self.valid_package(package): + self._current_package = None + self.failed = True + self.message = 'Invalid package: {0}.'.format(package) + raise HomebrewException(self.message) + + else: + self._current_package = package + return package + # /class properties -------------------------------------------- }}} + + def __init__(self, module, path=None, packages=None, state=None, + update_homebrew=False, ): + self._setup_status_vars() + self._setup_instance_vars(module=module, path=path, packages=packages, + state=state, update_homebrew=update_homebrew, ) + + self._prep() + + # prep --------------------------------------------------------- {{{ + def _setup_status_vars(self): + self.failed = False + self.changed = False + self.changed_count = 0 + self.unchanged_count = 0 + self.message = '' + + def _setup_instance_vars(self, **kwargs): + for key, val in kwargs.iteritems(): + setattr(self, key, val) + + def _prep(self): + self._prep_path() + self._prep_brew_path() + + def _prep_path(self): + if not self.path: + self.path = ['/usr/local/bin'] + + def _prep_brew_path(self): + if not self.module: + self.brew_path = None + self.failed = True + self.message = 'AnsibleModule not set.' + raise HomebrewException(self.message) + + self.brew_path = self.module.get_bin_path( + 'brew', + required=True, + opt_dirs=self.path, + ) + if not self.brew_path: + self.brew_path = None + self.failed = True + self.message = 'Unable to locate homebrew executable.' + raise HomebrewException('Unable to locate homebrew executable.') + + return self.brew_path + + def _status(self): + return (self.failed, self.changed, self.message) + # /prep -------------------------------------------------------- }}} + + def run(self): + try: + self._run() + except HomebrewException: + pass + + if not self.failed and (self.changed_count + self.unchanged_count > 1): + self.message = "Changed: %d, Unchanged: %d" % ( + self.changed_count, + self.unchanged_count, + ) + (failed, changed, message) = self._status() + + return (failed, changed, message) + + # checks ------------------------------------------------------- {{{ + def _current_package_is_installed(self): + if not self.valid_package(self.current_package): + self.failed = True + self.message = 'Invalid package: {0}.'.format(self.current_package) + raise HomebrewException(self.message) + + rc, out, err = self.module.run_command( + "{brew_path} list -m1 | grep -q '^{package}$'".format( + brew_path=self.brew_path, + package=self.current_package, + ) + ) + + if rc == 0: + return True + else: + return False + + def _outdated_packages(self): + rc, out, err = self.module.run_command([ + self.brew_path, + 'outdated', + ]) + return [line.split(' ')[0].strip() for line in out.split('\n') if line] + + def _current_package_is_outdated(self): + if not self.valid_package(self.current_package): + return False + + return self.current_package in self._outdated_packages() + + def _current_package_is_installed_from_head(self): + if not Homebrew.valid_package(self.current_package): + return False + elif not self._current_package_is_installed(): + return False + + rc, out, err = self.module.run_command([ + self.brew_path, + 'info', + self.current_package, + ]) + + try: + version_info = [line for line in out.split('\n') if line][0] + except IndexError: + return False + + return version_info.split(' ')[-1] == 'HEAD' + # /checks ------------------------------------------------------ }}} + + # commands ----------------------------------------------------- {{{ + def _run(self): + if self.update_homebrew: + self._update_homebrew() + + if self.packages: + if self.state == 'installed': + return self._install_packages() + elif self.state == 'upgraded': + return self._upgrade_packages() + elif self.state == 'head': + return self._install_packages() + # elif self.state == 'linked': + # return self._linked() + elif self.state == 'absent': + return self._uninstall_packages() + + # updated -------------------------------- {{{ + def _update_homebrew(self): + rc, out, err = self.module.run_command([ + self.brew_path, + 'update', + ]) + if rc == 0: + if out and isinstance(out, basestring): + already_updated = any( + re.search(r'Already up-to-date.', s.strip(), re.IGNORECASE) + for s in out.split('\n') + if s + ) + if not already_updated: + self.changed = True + self.message = 'Homebrew updated successfully.' + else: + self.message = 'Homebrew already up-to-date.' + + return True + else: + self.failed = True + self.message = err.strip() + raise HomebrewException(self.message) + # /updated ------------------------------- }}} + + # installed ------------------------------ {{{ + def _install_current_package(self): + if not self.valid_package(self.current_package): + self.failed = True + self.message = 'Invalid package: {0}.'.format(self.current_package) + raise HomebrewException(self.message) + + if self._current_package_is_installed(): + self.unchanged_count += 1 + self.message = 'Package already installed: {0}'.format( + self.current_package, + ) + return True + + if self.module.check_mode: + self.changed = True + self.message = 'Package would be installed: {0}'.format( + self.current_package + ) + raise HomebrewException(self.message) + + if self.state == 'head': + head = '--HEAD' + else: + head = None + + cmd = [opt + for opt in (self.brew_path, 'install', self.current_package, head) + if opt] + + rc, out, err = self.module.run_command(cmd) + + if self._current_package_is_installed(): + self.changed_count += 1 + self.changed = True + self.message = 'Package installed: {0}'.format(self.current_package) + return True + else: + self.failed = True + self.message = err.strip() + raise HomebrewException(self.message) + + def _install_packages(self): + for package in self.packages: + self.current_package = package + self._install_current_package() + + return True + # /installed ----------------------------- }}} + + # upgraded ------------------------------- {{{ + def _upgrade_current_package(self): + command = 'upgrade' + + if not self.valid_package(self.current_package): + self.failed = True + self.message = 'Invalid package: {0}.'.format(self.current_package) + raise HomebrewException(self.message) + + if not self._current_package_is_installed(): + command = 'install' + + if self._current_package_is_installed() and not self._current_package_is_outdated(): + self.message = 'Package is already upgraded: {0}'.format( + self.current_package, + ) + self.unchanged_count += 1 + return True + + if self.module.check_mode: + self.changed = True + self.message = 'Package would be upgraded: {0}'.format( + self.current_package + ) + raise HomebrewException(self.message) + + rc, out, err = self.module.run_command([ + self.brew_path, + command, + self.current_package, + ]) + + if not self._current_package_is_outdated(): + self.changed_count += 1 + self.changed = True + self.message = 'Package upgraded: {0}'.format(self.current_package) + return True + else: + self.failed = True + self.message = err.strip() + raise HomebrewException(self.message) + + def _upgrade_all_packages(self): + rc, out, err = self.module.run_command([ + self.brew_path, + 'upgrade', + ]) + if rc == 0: + self.changed = True + self.message = 'All packages upgraded.' + return True + else: + self.failed = True + self.message = err.strip() + raise HomebrewException(self.message) + + def _upgrade_packages(self): + if not self.packages: + self._upgrade_all_packages() + else: + for package in self.packages: + self.current_package = package + self._upgrade_current_package() + return True + # /upgraded ------------------------------ }}} + + # uninstalled ---------------------------- {{{ + def _uninstall_current_package(self): + if not self.valid_package(self.current_package): + self.failed = True + self.message = 'Invalid package: {0}.'.format(self.current_package) + raise HomebrewException(self.message) + + if not self._current_package_is_installed(): + self.unchanged_count += 1 + self.message = 'Package already uninstalled: {0}'.format( + self.current_package, + ) + return True + + if self.module.check_mode: + self.changed = True + self.message = 'Package would be uninstalled: {0}'.format( + self.current_package + ) + raise HomebrewException(self.message) + + cmd = [opt + for opt in (self.brew_path, 'uninstall', self.current_package) + if opt] + + rc, out, err = self.module.run_command(cmd) + + if not self._current_package_is_installed(): + self.changed_count += 1 + self.changed = True + self.message = 'Package uninstalled: {0}'.format(self.current_package) + return True + else: + self.failed = True + self.message = err.strip() + raise HomebrewException(self.message) + + def _uninstall_packages(self): + for package in self.packages: + self.current_package = package + self._uninstall_current_package() + + return True + # /uninstalled ----------------------------- }}} + # /commands ---------------------------------------------------- }}} -def remove_packages(module, brew_path, packages): - """ Uninstalls one or more packages if installed. """ - - removed_count = 0 - - # Using a for loop incase of error, we can report the package that failed - for package in packages: - # Query the package first, to see if we even need to remove. - if not query_package(module, brew_path, package): - continue - - if module.check_mode: - module.exit_json(changed=True) - rc, out, err = module.run_command([brew_path, 'remove', package]) - - if query_package(module, brew_path, package): - module.fail_json(msg="failed to remove %s: %s" % (package, out.strip())) - - removed_count += 1 - - if removed_count > 0: - module.exit_json(changed=True, msg="removed %d package(s)" % removed_count) - - module.exit_json(changed=False, msg="package(s) already absent") - - -def install_packages(module, brew_path, packages, options): - """ Installs one or more packages if not already installed. """ - - installed_count = 0 - - for package in packages: - if query_package(module, brew_path, package): - continue - - if module.check_mode: - module.exit_json(changed=True) - - cmd = [brew_path, 'install', package] - if options: - cmd.extend(options) - rc, out, err = module.run_command(cmd) - - if not query_package(module, brew_path, package): - module.fail_json(msg="failed to install %s: '%s' %s" % (package, cmd, out.strip())) - - installed_count += 1 - - if installed_count > 0: - module.exit_json(changed=True, msg="installed %d package(s)" % (installed_count,)) - - module.exit_json(changed=False, msg="package(s) already present") - -def generate_options_string(install_options): - if install_options is None: - return None - - options = [] - - for option in install_options: - options.append('--%s' % option) - - return options +# def link_package(module, brew_path, package): +# """ Links a single homebrew package. """ +# +# failed, changed, msg = False, False, '' +# +# if not a_valid_package(package): +# failed = True +# msg = 'invalid package' +# +# elif not query_package(module, brew_path, package): +# failed = True +# msg = 'not installed' +# +# else: +# if module.check_mode: +# module.exit_json(changed=True) +# +# rc, out, err = module.run_command([ +# brew_path, +# 'link', +# package, +# ]) +# +# if rc: +# failed = True +# msg = out.strip() +# else: +# if err.strip().lower().find('already linked') != -1: +# msg = 'already linked' +# else: +# changed = True +# msg = 'linked' +# +# return (failed, changed, msg) +# +# +# def link_packages(module, brew_path, packages): +# """ Upgrades one or more packages. """ +# +# failed, linked, unchanged, msg = False, 0, 0, '' +# +# for package in packages: +# failed, changed, msg = link_package(module, brew_path, package) +# if failed: +# break +# if changed: +# linked += 1 +# else: +# unchanged += 1 +# +# if failed: +# msg = 'installed: %d, unchanged: %d, error: ' + msg +# msg = msg % (linked, unchanged) +# elif linked: +# changed = True +# msg = 'linked: %d, unchanged: %d' % (linked, unchanged) +# else: +# msg = 'linked: %d, unchanged: %d' % (linked, unchanged) +# +# return (failed, changed, msg) +# +# +# def unlink_package(module, brew_path, package): +# """ Unlinks a single homebrew package. """ +# +# failed, changed, msg = False, False, '' +# +# if not a_valid_package(package): +# failed = True +# msg = 'invalid package' +# +# elif not query_package(module, brew_path, package): +# failed = True +# msg = 'not installed' +# +# else: +# if module.check_mode: +# module.exit_json(changed=True) +# +# rc, out, err = module.run_command([ +# brew_path, +# 'unlink', +# package, +# ]) +# +# if rc: +# failed = True +# msg = out.strip() +# else: +# if out.find('0 links') != -1: +# msg = 'already unlinked' +# else: +# changed = True +# msg = 'linked' +# +# return (failed, changed, msg) +# +# +# def unlink_packages(module, brew_path, packages): +# """ Unlinks one or more packages. """ +# +# failed, unlinked, unchanged, msg = False, 0, 0, '' +# +# for package in packages: +# failed, changed, msg = unlink_package(module, brew_path, package) +# if failed: +# break +# if changed: +# unlinked += 1 +# else: +# unchanged += 1 +# +# if failed: +# msg = 'installed: %d, unchanged: %d, error: ' + msg +# msg = msg % (unlinked, unchanged) +# elif unlinked: +# changed = True +# msg = 'unlinked: %d, unchanged: %d' % (unlinked, unchanged) +# else: +# msg = 'unlinked: %d, unchanged: %d' % (unlinked, unchanged) +# +# return (failed, changed, msg) def main(): module = AnsibleModule( - argument_spec = dict( - name = dict(aliases=["pkg"], required=True), - state = dict(default="present", choices=["present", "installed", "absent", "removed"]), - update_homebrew = dict(default="no", aliases=["update-brew"], type='bool'), - install_options = dict(default=None, aliases=["options"], type='list') + argument_spec=dict( + name=dict(aliases=["pkg"], required=False), + path=dict(required=False), + state=dict( + default="present", + choices=[ + "present", "installed", + "latest", "upgraded", "head", + "linked", "unlinked", + "absent", "removed", "uninstalled", + ], + ), + update_homebrew=dict( + default="no", + aliases=["update-brew"], + type='bool', + ), ), - supports_check_mode=True + supports_check_mode=True, ) - - brew_path = module.get_bin_path('brew', True, ['/usr/local/bin']) - p = module.params - if p["update_homebrew"]: - update_homebrew(module, brew_path) + if p['name']: + packages = p['name'].split(',') + else: + packages = None - pkgs = p["name"].split(",") + path = p['path'] + if path: + path = path.split(':') + else: + path = ['/usr/local/bin'] - if p["state"] in ["present", "installed"]: - opt = generate_options_string(p["install_options"]) - install_packages(module, brew_path, pkgs, opt) + state = p['state'] + if state in ('present', 'installed', 'head'): + state = 'installed' + if state in ('latest', 'upgraded'): + state = 'upgraded' + if state in ('absent', 'removed', 'uninstalled'): + state = 'absent' - elif p["state"] in ["absent", "removed"]: - remove_packages(module, brew_path, pkgs) + update_homebrew = p['update_homebrew'] -# import module snippets -from ansible.module_utils.basic import * + brew = Homebrew(module=module, path=path, packages=packages, + state=state, update_homebrew=update_homebrew) + (failed, changed, message) = brew.run() + if failed: + module.fail_json(msg=message) + else: + module.exit_json(changed=changed, msg=message) +# this is magic, see lib/ansible/module_common.py +#<> main() From 91ac44a5bbb7da83b8aacd9f1a5d7726b7fc10b0 Mon Sep 17 00:00:00 2001 From: Daniel Jaouen Date: Wed, 19 Feb 2014 15:03:47 -0500 Subject: [PATCH 2/7] Fix linked/unlinked states. --- library/packaging/homebrew | 208 +++++++++++++++---------------------- 1 file changed, 82 insertions(+), 126 deletions(-) diff --git a/library/packaging/homebrew b/library/packaging/homebrew index feea2dc8e03..dfb82fa8c8a 100644 --- a/library/packaging/homebrew +++ b/library/packaging/homebrew @@ -171,6 +171,7 @@ class Homebrew(object): - upgraded - head - linked + - unlinked - absent ''' @@ -184,6 +185,7 @@ class Homebrew(object): 'upgraded', 'head', 'linked', + 'unlinked', 'absent', ) ) @@ -406,8 +408,10 @@ class Homebrew(object): return self._upgrade_packages() elif self.state == 'head': return self._install_packages() - # elif self.state == 'linked': - # return self._linked() + elif self.state == 'linked': + return self._link_packages() + elif self.state == 'unlinked': + return self._unlink_packages() elif self.state == 'absent': return self._uninstall_packages() @@ -597,133 +601,81 @@ class Homebrew(object): return True # /uninstalled ----------------------------- }}} + + # linked --------------------------------- {{{ + def _link_current_package(self): + if not self.valid_package(self.current_package): + self.failed = True + self.message = 'Invalid package: {0}.'.format(self.current_package) + raise HomebrewException(self.message) + + if not self._current_package_is_installed(): + self.failed = True + self.message = 'Package not installed: {0}.'.format(self.current_package) + raise HomebrewException(self.message) + + if self.module.check_mode: + self.changed = True + self.message = 'Package would be linked: {0}'.format( + self.current_package + ) + raise HomebrewException(self.message) + + cmd = [opt + for opt in (self.brew_path, 'link', self.current_package) + if opt] + + rc, out, err = self.module.run_command(cmd) + self.changed_count += 1 + self.changed = True + self.message = 'Package linked: {0}'.format(self.current_package) + + def _link_packages(self): + for package in self.packages: + self.current_package = package + self._link_current_package() + + return True + # /linked -------------------------------- }}} + + # unlinked ------------------------------- {{{ + def _unlink_current_package(self): + if not self.valid_package(self.current_package): + self.failed = True + self.message = 'Invalid package: {0}.'.format(self.current_package) + raise HomebrewException(self.message) + + if not self._current_package_is_installed(): + self.failed = True + self.message = 'Package not installed: {0}.'.format(self.current_package) + raise HomebrewException(self.message) + + if self.module.check_mode: + self.changed = True + self.message = 'Package would be unlinked: {0}'.format( + self.current_package + ) + raise HomebrewException(self.message) + + cmd = [opt + for opt in (self.brew_path, 'unlink', self.current_package) + if opt] + + rc, out, err = self.module.run_command(cmd) + self.changed_count += 1 + self.changed = True + self.message = 'Package unlinked: {0}'.format(self.current_package) + + def _unlink_packages(self): + for package in self.packages: + self.current_package = package + self._unlink_current_package() + + return True + # /unlinked ------------------------------ }}} # /commands ---------------------------------------------------- }}} -# def link_package(module, brew_path, package): -# """ Links a single homebrew package. """ -# -# failed, changed, msg = False, False, '' -# -# if not a_valid_package(package): -# failed = True -# msg = 'invalid package' -# -# elif not query_package(module, brew_path, package): -# failed = True -# msg = 'not installed' -# -# else: -# if module.check_mode: -# module.exit_json(changed=True) -# -# rc, out, err = module.run_command([ -# brew_path, -# 'link', -# package, -# ]) -# -# if rc: -# failed = True -# msg = out.strip() -# else: -# if err.strip().lower().find('already linked') != -1: -# msg = 'already linked' -# else: -# changed = True -# msg = 'linked' -# -# return (failed, changed, msg) -# -# -# def link_packages(module, brew_path, packages): -# """ Upgrades one or more packages. """ -# -# failed, linked, unchanged, msg = False, 0, 0, '' -# -# for package in packages: -# failed, changed, msg = link_package(module, brew_path, package) -# if failed: -# break -# if changed: -# linked += 1 -# else: -# unchanged += 1 -# -# if failed: -# msg = 'installed: %d, unchanged: %d, error: ' + msg -# msg = msg % (linked, unchanged) -# elif linked: -# changed = True -# msg = 'linked: %d, unchanged: %d' % (linked, unchanged) -# else: -# msg = 'linked: %d, unchanged: %d' % (linked, unchanged) -# -# return (failed, changed, msg) -# -# -# def unlink_package(module, brew_path, package): -# """ Unlinks a single homebrew package. """ -# -# failed, changed, msg = False, False, '' -# -# if not a_valid_package(package): -# failed = True -# msg = 'invalid package' -# -# elif not query_package(module, brew_path, package): -# failed = True -# msg = 'not installed' -# -# else: -# if module.check_mode: -# module.exit_json(changed=True) -# -# rc, out, err = module.run_command([ -# brew_path, -# 'unlink', -# package, -# ]) -# -# if rc: -# failed = True -# msg = out.strip() -# else: -# if out.find('0 links') != -1: -# msg = 'already unlinked' -# else: -# changed = True -# msg = 'linked' -# -# return (failed, changed, msg) -# -# -# def unlink_packages(module, brew_path, packages): -# """ Unlinks one or more packages. """ -# -# failed, unlinked, unchanged, msg = False, 0, 0, '' -# -# for package in packages: -# failed, changed, msg = unlink_package(module, brew_path, package) -# if failed: -# break -# if changed: -# unlinked += 1 -# else: -# unchanged += 1 -# -# if failed: -# msg = 'installed: %d, unchanged: %d, error: ' + msg -# msg = msg % (unlinked, unchanged) -# elif unlinked: -# changed = True -# msg = 'unlinked: %d, unchanged: %d' % (unlinked, unchanged) -# else: -# msg = 'unlinked: %d, unchanged: %d' % (unlinked, unchanged) -# -# return (failed, changed, msg) - - def main(): module = AnsibleModule( argument_spec=dict( @@ -764,6 +716,10 @@ def main(): state = 'installed' if state in ('latest', 'upgraded'): state = 'upgraded' + if state == 'linked': + state = 'linked' + if state == 'unlinked': + state = 'unlinked' if state in ('absent', 'removed', 'uninstalled'): state = 'absent' From 097ce843366cbf7dfd1a413e27266de15dbd4204 Mon Sep 17 00:00:00 2001 From: Daniel Jaouen Date: Wed, 19 Feb 2014 15:15:37 -0500 Subject: [PATCH 3/7] Fix homebrew linked/unlinked output. --- library/packaging/homebrew | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/library/packaging/homebrew b/library/packaging/homebrew index dfb82fa8c8a..9609ce4068b 100644 --- a/library/packaging/homebrew +++ b/library/packaging/homebrew @@ -626,9 +626,17 @@ class Homebrew(object): if opt] rc, out, err = self.module.run_command(cmd) - self.changed_count += 1 - self.changed = True - self.message = 'Package linked: {0}'.format(self.current_package) + + if rc == 0: + self.changed_count += 1 + self.changed = True + self.message = 'Package linked: {0}'.format(self.current_package) + + return True + else: + self.failed = True + self.message = 'Package could not be linked: {0}.'.format(self.current_package) + raise HomebrewException(self.message) def _link_packages(self): for package in self.packages: @@ -662,9 +670,17 @@ class Homebrew(object): if opt] rc, out, err = self.module.run_command(cmd) - self.changed_count += 1 - self.changed = True - self.message = 'Package unlinked: {0}'.format(self.current_package) + + if rc == 0: + self.changed_count += 1 + self.changed = True + self.message = 'Package unlinked: {0}'.format(self.current_package) + + return True + else: + self.failed = True + self.message = 'Package could not be unlinked: {0}.'.format(self.current_package) + raise HomebrewException(self.message) def _unlink_packages(self): for package in self.packages: From 155f6e4dd3d597253e787c5eb191c6bde5d3dab8 Mon Sep 17 00:00:00 2001 From: Daniel Jaouen Date: Wed, 19 Feb 2014 17:06:58 -0500 Subject: [PATCH 4/7] Update homebrew documentation. --- library/packaging/homebrew | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/packaging/homebrew b/library/packaging/homebrew index 9609ce4068b..dcd2c048a98 100644 --- a/library/packaging/homebrew +++ b/library/packaging/homebrew @@ -22,7 +22,7 @@ DOCUMENTATION = ''' --- module: homebrew -author: Andrew Dunham +author: Andrew Dunham and Daniel Jaouen short_description: Package manager for Homebrew description: - Manages Homebrew packages @@ -35,7 +35,7 @@ options: state: description: - state of the package - choices: [ 'head', 'latest', 'installed', 'linked', 'uninstalled' ] + choices: [ 'head', 'latest', 'present', 'absent', 'linked', 'uninstalled' ] required: false default: present update_homebrew: @@ -44,6 +44,11 @@ options: required: false default: "no" choices: [ "yes", "no" ] + install_options: + description: + - options flags to install a package + required: false + default: null notes: [] ''' EXAMPLES = ''' @@ -55,6 +60,7 @@ EXAMPLES = ''' - homebrew: name=foo state=linked - homebrew: name=foo state=absent - homebrew: name=foo,bar state=absent +- homebrew: name=foo state=present install_options=with-baz,enable-debug ''' import os.path From e3a39837e17d78467469cfb1ba4af4712aa88158 Mon Sep 17 00:00:00 2001 From: Daniel Jaouen Date: Wed, 19 Feb 2014 17:40:26 -0500 Subject: [PATCH 5/7] Add back homebrew `install_options` parameter. --- library/packaging/homebrew | 81 +++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/library/packaging/homebrew b/library/packaging/homebrew index dcd2c048a98..dea962159a9 100644 --- a/library/packaging/homebrew +++ b/library/packaging/homebrew @@ -284,10 +284,13 @@ class Homebrew(object): # /class properties -------------------------------------------- }}} def __init__(self, module, path=None, packages=None, state=None, - update_homebrew=False, ): + update_homebrew=False, install_options=None): + if not install_options: + install_options = list() self._setup_status_vars() self._setup_instance_vars(module=module, path=path, packages=packages, - state=state, update_homebrew=update_homebrew, ) + state=state, update_homebrew=update_homebrew, + install_options=install_options, ) self._prep() @@ -473,10 +476,12 @@ class Homebrew(object): else: head = None - cmd = [opt - for opt in (self.brew_path, 'install', self.current_package, head) - if opt] - + opts = ( + [self.brew_path, 'install'] + + self.install_options + + [self.current_package, head] + ) + cmd = [opt for opt in opts if opt] rc, out, err = self.module.run_command(cmd) if self._current_package_is_installed(): @@ -523,11 +528,13 @@ class Homebrew(object): ) raise HomebrewException(self.message) - rc, out, err = self.module.run_command([ - self.brew_path, - command, - self.current_package, - ]) + opts = ( + [self.brew_path, command] + + self.install_options + + [self.current_package] + ) + cmd = [opt for opt in opts if opt] + rc, out, err = self.module.run_command(cmd) if not self._current_package_is_outdated(): self.changed_count += 1 @@ -540,10 +547,13 @@ class Homebrew(object): raise HomebrewException(self.message) def _upgrade_all_packages(self): - rc, out, err = self.module.run_command([ - self.brew_path, - 'upgrade', - ]) + opts = ( + [self.brew_path, 'upgrade'] + + self.install_options + ) + cmd = [opt for opt in opts if opt] + rc, out, err = self.module.run_command(cmd) + if rc == 0: self.changed = True self.message = 'All packages upgraded.' @@ -584,10 +594,12 @@ class Homebrew(object): ) raise HomebrewException(self.message) - cmd = [opt - for opt in (self.brew_path, 'uninstall', self.current_package) - if opt] - + opts = ( + [self.brew_path, 'uninstall'] + + self.install_options + + [self.current_package] + ) + cmd = [opt for opt in opts if opt] rc, out, err = self.module.run_command(cmd) if not self._current_package_is_installed(): @@ -627,10 +639,12 @@ class Homebrew(object): ) raise HomebrewException(self.message) - cmd = [opt - for opt in (self.brew_path, 'link', self.current_package) - if opt] - + opts = ( + [self.brew_path, 'link'] + + self.install_options + + [self.current_package] + ) + cmd = [opt for opt in opts if opt] rc, out, err = self.module.run_command(cmd) if rc == 0: @@ -671,10 +685,12 @@ class Homebrew(object): ) raise HomebrewException(self.message) - cmd = [opt - for opt in (self.brew_path, 'unlink', self.current_package) - if opt] - + opts = ( + [self.brew_path, 'unlink'] + + self.install_options + + [self.current_package] + ) + cmd = [opt for opt in opts if opt] rc, out, err = self.module.run_command(cmd) if rc == 0: @@ -717,6 +733,11 @@ def main(): aliases=["update-brew"], type='bool', ), + install_options=dict( + default=None, + aliases=['options'], + type='list', + ) ), supports_check_mode=True, ) @@ -746,9 +767,13 @@ def main(): state = 'absent' update_homebrew = p['update_homebrew'] + p['install_options'] = p['install_options'] or [] + install_options = ['--{0}'.format(install_option) + for install_option in p['install_options']] brew = Homebrew(module=module, path=path, packages=packages, - state=state, update_homebrew=update_homebrew) + state=state, update_homebrew=update_homebrew, + install_options=install_options) (failed, changed, message) = brew.run() if failed: module.fail_json(msg=message) From 6748ef121b2fb097e5a4c8b7b65f42fc6d6df38d Mon Sep 17 00:00:00 2001 From: Daniel Jaouen Date: Wed, 19 Feb 2014 17:43:09 -0500 Subject: [PATCH 6/7] Update homebrew documentation. --- library/packaging/homebrew | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/packaging/homebrew b/library/packaging/homebrew index dea962159a9..33b2ab62497 100644 --- a/library/packaging/homebrew +++ b/library/packaging/homebrew @@ -35,7 +35,7 @@ options: state: description: - state of the package - choices: [ 'head', 'latest', 'present', 'absent', 'linked', 'uninstalled' ] + choices: [ 'head', 'latest', 'present', 'absent', 'linked', 'unlinked' ] required: false default: present update_homebrew: From 81f2e43b76e108b57e6916d902d3a19799cfe1c6 Mon Sep 17 00:00:00 2001 From: Daniel Jaouen Date: Wed, 12 Mar 2014 19:52:16 -0400 Subject: [PATCH 7/7] Fix Homebrew._current_package_is_installed --- library/packaging/homebrew | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/library/packaging/homebrew b/library/packaging/homebrew index 33b2ab62497..a74091542e2 100644 --- a/library/packaging/homebrew +++ b/library/packaging/homebrew @@ -360,14 +360,15 @@ class Homebrew(object): self.message = 'Invalid package: {0}.'.format(self.current_package) raise HomebrewException(self.message) - rc, out, err = self.module.run_command( - "{brew_path} list -m1 | grep -q '^{package}$'".format( - brew_path=self.brew_path, - package=self.current_package, - ) - ) + cmd = [ + "{brew_path}".format(brew_path=self.brew_path), + "list", + "-m1", + ] + rc, out, err = self.module.run_command(cmd) + packages = [package for package in out.split('\n') if package] - if rc == 0: + if rc == 0 and self.current_package in packages: return True else: return False