From 441c30b2454323a9f228ac79660f938f4a7a72a8 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Mon, 10 Mar 2014 23:52:42 -0600 Subject: [PATCH] updates for ansible 1.5 compatibility; update --version option --- bin/battle | 15 +++- lib/battleschool/__init__.py | 2 +- lib/battleschool/printing.py | 17 +++-- share/defaults/battleschool.yml | 3 - share/library/mac_pkg | 118 ++++++++++++++++++++++++++------ test/test_pkg_vagrant.sh | 8 +++ 6 files changed, 128 insertions(+), 35 deletions(-) create mode 100755 test/test_pkg_vagrant.sh diff --git a/bin/battle b/bin/battle index d1d7922..a8a399f 100755 --- a/bin/battle +++ b/bin/battle @@ -2,7 +2,6 @@ import os import sys -import subprocess import ansible.constants as AC import battleschool.constants as C @@ -16,6 +15,7 @@ from ansible import callbacks from ansible import errors from ansible import utils +from battleschool.__init__ import __version__ from battleschool.printing import * from battleschool.source.git import Git from battleschool.source.local import Local @@ -91,6 +91,7 @@ def main(args): diff_opts=True, output_opts=True ) + parser.version = "%s %s" % ("battleschool", __version__) parser.add_option('-e', '--extra-vars', dest="extra_vars", default=None, help="set additional key=value variables from the CLI") parser.add_option('--tags', dest='tags', default='all', @@ -111,8 +112,9 @@ def main(args): help="update playbooks from sources(git, url, etc...)") options, args = parser.parse_args(args) + # options.connection = 'local' - playbooks_to_run = [C.DEFAULT_PLAYBOOK] + playbooks_to_run = []#[C.DEFAULT_PLAYBOOK] #----------------------------------------------------------- # setup inventory @@ -129,7 +131,9 @@ def main(args): if not options.listhosts and not options.syntax and not options.listtasks: options.ask_pass = AC.DEFAULT_ASK_PASS options.ask_sudo_pass = options.ask_sudo_pass or AC.DEFAULT_ASK_SUDO_PASS - ( sshpass, sudopass ) = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass) + passwds = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass) + sshpass = passwds[0] + sudopass = passwds[1] # if options.sudo_user or options.ask_sudo_pass: # options.sudo = True options.sudo_user = AC.DEFAULT_SUDO_USER @@ -169,6 +173,8 @@ def main(args): else: options.cache_dir = "%s/cache" % battleschool_dir + os.environ["BATTLESCHOOL_CACHE_DIR"] = options.cache_dir + #----------------------------------------------------------- # setup extra_vars for later use if extra_vars is None: @@ -216,6 +222,9 @@ def main(args): runner_cb = BattleschoolRunnerCallbacks() playbook_cb = BattleschoolCallbacks() + #TODO: option to use default callbacks + # runner_cb = callbacks.PlaybookRunnerCallbacks(stats, verbose=utils.VERBOSITY) + # playbook_cb = callbacks.PlaybookCallbacks(verbose=utils.VERBOSITY) if options.step: playbook_cb.step = options.step diff --git a/lib/battleschool/__init__.py b/lib/battleschool/__init__.py index 6e7a839..4aef767 100644 --- a/lib/battleschool/__init__.py +++ b/lib/battleschool/__init__.py @@ -1,2 +1,2 @@ -__version__ = '0.2.2' +__version__ = '0.3.0' __author__ = 'Spencer Gibb' diff --git a/lib/battleschool/printing.py b/lib/battleschool/printing.py index f871d93..90ec90a 100644 --- a/lib/battleschool/printing.py +++ b/lib/battleschool/printing.py @@ -71,9 +71,14 @@ def banner(msg): class BattleschoolRunnerCallbacks(DefaultRunnerCallbacks): """ callbacks for use by battles """ + def get_play(self): + if self.task: + return self.task + return self.play + def on_failed(self, host, res, ignore_errors=False): if not ignore_errors: - display("\tTask FAILED: %s %s" % (self.task.name, res['msg']), color="red") + display("\tTask FAILED: %s %s" % (self.get_play().name, res['msg']), color="red") super(BattleschoolRunnerCallbacks, self).on_failed(host, res, ignore_errors=ignore_errors) def on_ok(self, host, res): @@ -86,8 +91,8 @@ def on_ok(self, host, res): msg = '' try: - if self.task: - display("\tTask OK: %s%s" % (self.task.name, msg)) + if self.get_play(): + display("\tTask OK: %s%s" % (self.get_play().name, msg)) except AttributeError: invocation = res['invocation'] msg = invocation['module_args'] @@ -117,15 +122,15 @@ def on_unreachable(self, host, results): super(BattleschoolRunnerCallbacks, self).on_unreachable(host, results) def on_skipped(self, host, item=None): - display("\tTask skipped: %s" % self.task.name, color="yellow") + display("\tTask skipped: %s" % self.get_play().name, color="yellow") super(BattleschoolRunnerCallbacks, self).on_skipped(host, item) def on_error(self, host, err): - display("\tTask ERROR: %s%s" % (self.task.name, err), color="red") + display("\tTask ERROR: %s%s" % (self.get_play().name, err), color="red") super(BattleschoolRunnerCallbacks, self).on_error(host, err) def on_no_hosts(self): - display("\tTask NO HOSTS: %s" % self.task.name, color="red") + display("\tTask NO HOSTS: %s" % self.get_play().name, color="red") super(BattleschoolRunnerCallbacks, self).on_no_hosts() def on_async_poll(self, host, res, jid, clock): diff --git a/share/defaults/battleschool.yml b/share/defaults/battleschool.yml index a3b252b..bbef28c 100644 --- a/share/defaults/battleschool.yml +++ b/share/defaults/battleschool.yml @@ -6,6 +6,3 @@ tasks: - name: print from playbook debug: msg="in battleschool.yml" - - #- include: {{item}} - # with_items: playbooks \ No newline at end of file diff --git a/share/library/mac_pkg b/share/library/mac_pkg index 3a4cb16..49b064b 100644 --- a/share/library/mac_pkg +++ b/share/library/mac_pkg @@ -27,11 +27,65 @@ import abc import hashlib import os import shutil +import subprocess import tempfile +import traceback from os import path +def run_command(module, args, check_rc=False, close_fds=False, executable=None, data=None, cwd=None): + ''' + Execute a command, returns rc, stdout, and stderr. + args is the command to run + If args is a list, the command will be run with shell=False. + Otherwise, the command will be run with shell=True when args is a string. + Other arguments: + - check_rc (boolean) Whether to call fail_json in case of + non zero RC. Default is False. + - close_fds (boolean) See documentation for subprocess.Popen(). + Default is False. + - executable (string) See documentation for subprocess.Popen(). + Default is None. + - cwd (string) See documentation for subprocess.Popen(). + Default is None. + ''' + if isinstance(args, list): + shell = False + elif isinstance(args, basestring): + shell = True + else: + msg = "Argument 'args' to run_command must be list or string" + module.fail_json(rc=257, cmd=args, msg=msg) + rc = 0 + msg = None + st_in = None + if data: + st_in = subprocess.PIPE + try: + cmd = subprocess.Popen(args, + executable=executable, + shell=shell, + close_fds=close_fds, + stdin=st_in, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=cwd) + if data: + cmd.stdin.write(data) + cmd.stdin.write('\n') + out, err = cmd.communicate() + rc = cmd.returncode + except (OSError, IOError), e: + module.fail_json(rc=e.errno, msg=str(e), cmd=args) + except: + module.fail_json(rc=257, msg=traceback.format_exc(), cmd=args) + if rc != 0 and check_rc: + msg = err.rstrip() + module.fail_json(cmd=args, rc=rc, stdout=out, stderr=err, msg=msg) + return rc, out, err + + class Archive(object): __metaclass__ = abc.ABCMeta @@ -42,6 +96,7 @@ class Archive(object): self.curl_path = module.get_bin_path('curl', True, ['/usr/bin']) self.chown_path = module.get_bin_path('chown', True) self.pkg = pkg + self._pkg_path = None return @abc.abstractmethod @@ -62,6 +117,12 @@ class Archive(object): return self.params['url'] + def chown_dir(self, chown_user, target_path): + if chown_user is not None: + rc, out, err = self.module.run_command("%s -R %s %s" % (self.chown_path, chown_user, target_path)) + if rc > 0: + self.module.fail_json(msg="failed to install %s: %s\n\t%s" % (self.pkg.name(), out, err)) + def acquire(self): acquired = False if exists(self.params, 'src'): @@ -83,11 +144,6 @@ class Archive(object): if exists(self.params, 'curl_opts'): curl_opts = self.params['curl_opts'] - #TODO: use environment variable to override cachedir - download_dir = path.expanduser("~/.battleschool/cache/downloads") - if not path.exists(download_dir): - os.makedirs(download_dir) - #TODO: make chown optional? if exists(os.environ, 'SUDO_USER'): chown_user = os.environ['SUDO_USER'] @@ -95,6 +151,15 @@ class Archive(object): chown_user = None # self.module.exit_json(changed=False, msg="chown_user %s" % chown_user) + #TODO: use environment variable to override cachedir + download_dir = path.expanduser("~/.battleschool/cache/downloads") + if not path.exists(download_dir): + os.makedirs(download_dir) + #TODO: only chown downloaded item, only chown if just downloaded + self.chown_dir(chown_user, download_dir) + # self.module.exit_json(changed=False, msg="download_dir %s" % download_dir) + # self.module.exit_json(changed=False, msg="BATTLESCHOOL_CACHE_DIR %s" % os.environ.get('BATTLESCHOOL_CACHE_DIR', "")) + #TODO: verify write perms of dest parent if exists(self.params, 'dest'): dest = self.params['dest'] @@ -102,7 +167,7 @@ class Archive(object): if not path.isabs(dest): dest = "%s/%s" % (download_dir, dest) - pre_cmd = "" + cwd = None output_opts = "--output %s" % dest do_download = not path.exists(dest) else: @@ -110,8 +175,9 @@ class Archive(object): dest = "%s/%s" % (download_dir, sha1) if not path.exists(dest): os.makedirs(dest) + self.chown_dir(chown_user, dest) do_download = not os.listdir(dest) - pre_cmd = "cd %s && " % dest + cwd = dest output_opts = "--remote-name --remote-header-name" # self.module.exit_json(changed=False, msg="pre_cmd %s, output_opts %s, curl_opts %s, do_download %s" @@ -129,8 +195,10 @@ class Archive(object): os.unlink(file_path) if do_download: - rc, out, err = self.module.run_command("%s %s --insecure --silent --location %s %s %s" % - (pre_cmd, self.curl_path, output_opts, curl_opts, url)) + download_cmd = "%s --insecure --silent --location %s %s %s" % ( + self.curl_path, output_opts, curl_opts, url) + # self.module.exit_json(changed=False, msg="download_cmd %s" % download_cmd) + rc, out, err = run_command(self.module, download_cmd, cwd=cwd) if rc > 0: self.module.fail_json(msg="failed to install %s: %s\n\t%s" % (self.pkg.name(), out, err)) acquired = True @@ -138,6 +206,7 @@ class Archive(object): if not exists(self.params, 'dest'): # need to find out the path of the downloaded file files = os.listdir(dest) + # self.module.exit_json(changed=False, msg="files %s" % files) num_files = len(files) if num_files != 1: @@ -145,13 +214,11 @@ class Archive(object): % (self.pkg.name(), num_files, dest)) dest = "%s/%s" % (dest, files[0]) - # self.module.exit_json(changed=False, msg="dest %s" % dest) + # self.module.exit_json(changed=False, msg="dest %s" % dest) - #TODO: only chown downloaded item, only chown if just downloaded - if chown_user is not None: - rc, out, err = self.module.run_command("%s -R %s %s" % (self.chown_path, chown_user, download_dir)) - if rc > 0: - self.module.fail_json(msg="failed to install %s: %s\n\t%s" % (self.pkg.name(), out, err)) + #TODO: only chown downloaded item + if do_download and acquired: + self.chown_dir(chown_user, download_dir) self._pkg_path = dest @@ -196,8 +263,9 @@ class DmgArchive(Archive): #if dmg mount and record volume path #hdiutil attach Vagrant-1.3.0.dmg | grep Volumes | awk '{print $3}' - command = "%s %s attach %s %s " % (hdi_pre, self.hdiutil_path, self._pkg_path, hdi_post) - rc, out, err = self.module.run_command(command) + command = "%s %s attach \"%s\" %s " % (hdi_pre, self.hdiutil_path, self._pkg_path, hdi_post) + # self.module.exit_json(changed=False, msg="hdicmd: %s" % command) + rc, out, err = run_command(self.module, command) if rc > 0: self.module.fail_json(msg="failed to install %s: rc: %s, %s, err: %s" % (self.pkg.name(), rc, out, err)) @@ -256,14 +324,17 @@ class PkgPackage(Package): self.pkgutil_path = module.get_bin_path('pkgutil', True, ['/usr/sbin']) #TODO: add creates which would override pkg_version - #find installed version self._pkg_name = self.params['pkg_name'] - rc, out, err = module.run_command("%s --pkg-info=%s 2>&1 |grep version| awk '{print $2}'" % - (self.pkgutil_path, self._pkg_name)) + #find installed version + self._version = self.find_version(module) + # self.module.exit_json(changed=False, msg="_version %s, _pkg_name %s" % (self._version, self._pkg_name)) + + def find_version(self, module): + rc, out, err = run_command(self.module, "%s --pkg-info=%s 2>&1 |grep version| awk '{print $2}'" % + (self.pkgutil_path, self._pkg_name)) if rc > 0: module.fail_json(msg="failed to install %s: %s %s" % (self._pkg_name, out, err)) - - self._version = out.strip() + return out.strip() def install(self, pkg_path): #install package file @@ -273,6 +344,8 @@ class PkgPackage(Package): if rc > 0: return out, err + #no error, update the version from pkg_util + self._version = self.find_version(self.module) return False def is_installed(self): @@ -366,6 +439,7 @@ class Installer(object): def install(self, acquire_only): pkg = self._instantiate('pkg_type', 'Package') + # self.module.exit_json(changed=False, msg="params %s" % self.params) if pkg.is_installed() and not acquire_only: self.module.exit_json(changed=False, version=pkg.version(), msg="package %s already present" % pkg.name()) diff --git a/test/test_pkg_vagrant.sh b/test/test_pkg_vagrant.sh new file mode 100755 index 0000000..0c544ad --- /dev/null +++ b/test/test_pkg_vagrant.sh @@ -0,0 +1,8 @@ +MODULE_PARAMS="pkg_name=com.vagrant.vagrant" +MODULE_PARAMS="$MODULE_PARAMS pkg_version=1.5.0" +MODULE_PARAMS="$MODULE_PARAMS archive_type=dmg" +MODULE_PARAMS="$MODULE_PARAMS archive_path=Vagrant.pkg" +#MODULE_PARAMS="$MODULE_PARAMS force=true" +MODULE_PARAMS="$MODULE_PARAMS url=https://dl.bintray.com/mitchellh/vagrant/vagrant_1.5.0.dmg" +#echo $MODULE_PARAMS +~/workspace/32degrees/ansible/hacking/test-module -m share/library/mac_pkg -a "$MODULE_PARAMS"