From f11de2f5c95d691c0cc62f7b6a3e3e11b54e5ac1 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 30 Mar 2012 22:28:10 -0400 Subject: [PATCH] --extra-vars option for ansible-playbook Conflicts: lib/ansible/playbook.py Removed unneccessary shlex and replaced with basic split, some repurcussions in runner that can be eliminated once we consistently pass args as a string (soon). --- bin/ansible-playbook | 3 +++ lib/ansible/playbook.py | 21 +++++++++++++++------ lib/ansible/runner.py | 34 ++++++++++++++++++++++++++-------- lib/ansible/utils.py | 8 +++++++- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/bin/ansible-playbook b/bin/ansible-playbook index cd276bff97f..a4071d5e573 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -37,6 +37,8 @@ def main(args): help='set the number of forks to start up') parser.add_option("-i", "--inventory-file", dest="inventory", help="inventory host file", default=C.DEFAULT_HOST_LIST) + parser.add_option('-e', '--extra-vars', dest='extra_vars', + help='arguments to pass to the inventory script') parser.add_option("-k", "--ask-pass", default=False, action="store_true", help="ask for SSH password") parser.add_option("-M", "--module-path", dest="module_path", @@ -71,6 +73,7 @@ def main(args): pb = ansible.playbook.PlayBook( playbook=playbook, host_list=options.inventory, + extra_vars=options.extra_vars, module_path=options.module_path, forks=options.forks, verbose=True, diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 6c9ef9fb3f5..494b09cc809 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -56,6 +56,7 @@ class PlayBook(object): remote_pass = C.DEFAULT_REMOTE_PASS, remote_port = C.DEFAULT_REMOTE_PORT, override_hosts = None, + extra_vars = None, verbose = False, callbacks = None, runner_callbacks = None, @@ -75,13 +76,14 @@ class PlayBook(object): self.callbacks = callbacks self.runner_callbacks = runner_callbacks self.override_hosts = override_hosts + self.extra_vars = extra_vars self.stats = stats self.basedir = os.path.dirname(playbook) self.playbook = self._parse_playbook(playbook) self.host_list, self.groups = ansible.runner.Runner.parse_hosts( - host_list, override_hosts=self.override_hosts) + host_list, override_hosts=self.override_hosts, extra_vars=self.extra_vars) # ***************************************************** @@ -267,8 +269,8 @@ class PlayBook(object): timeout=self.timeout, remote_user=remote_user, remote_port=self.remote_port, setup_cache=SETUP_CACHE, basedir=self.basedir, - conditional=only_if, callbacks=self.runner_callbacks, - sudo=sudo + conditional=only_if, callbacks=self.runner_callbacks, + extra_vars=self.extra_vars, sudo=sudo ) if async_seconds == 0: @@ -286,16 +288,16 @@ class PlayBook(object): name = task.get('name', None) action = task.get('action', None) if action is None: - raise errors.AnsibleError("action is required for each item in tasks") + raise errors.AnsibleError("action is required for each item in tasks: offending task is %s" % name if name else "unknown") if name is None: name = action only_if = task.get('only_if', 'True') async_seconds = int(task.get('async', 0)) # not async by default async_poll_interval = int(task.get('poll', 10)) # default poll = 10 seconds - tokens = shlex.split(action, posix=False) + tokens = action.split(None, 1) module_name = tokens[0] - module_args = tokens[1:] + module_args = tokens[1] # tasks can be direct (run on all nodes matching # the pattern) or conditional, where they ran @@ -445,6 +447,13 @@ class PlayBook(object): for (host, result) in setup_ok.iteritems(): SETUP_CACHE[host] = result + if self.extra_vars: + extra_vars = utils.parse_kv(shlex.split(self.extra_vars)) + for h in self.host_list: + try: + SETUP_CACHE[h].update(extra_vars) + except: + SETUP_CACHE[h] = extra_vars return host_list # ***************************************************** diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index 569de4595a3..2d7ea7a11ce 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -74,7 +74,7 @@ class Runner(object): remote_user=C.DEFAULT_REMOTE_USER, remote_pass=C.DEFAULT_REMOTE_PASS, remote_port=C.DEFAULT_REMOTE_PORT, background=0, basedir=None, setup_cache=None, transport='paramiko', conditional='True', groups={}, callbacks=None, verbose=False, - sudo=False): + sudo=False, extra_vars=None): if setup_cache is None: setup_cache = {} @@ -101,6 +101,7 @@ class Runner(object): self.forks = int(forks) self.pattern = pattern self.module_args = module_args + self.extra_vars = extra_vars self.timeout = timeout self.verbose = verbose self.remote_user = remote_user @@ -143,14 +144,17 @@ class Runner(object): # ***************************************************** @classmethod - def parse_hosts_from_script(cls, host_list): + def parse_hosts_from_script(cls, host_list, extra_vars): ''' evaluate a script that returns list of hosts by groups ''' results = [] groups = dict(ungrouped=[]) host_list = os.path.abspath(host_list) cls._external_variable_script = host_list - cmd = subprocess.Popen([host_list], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False) + cmd = [host_list, '--list'] + if extra_vars: + cmd.extend(['--extra-vars', extra_vars]) + cmd = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False) out, err = cmd.communicate() try: groups = utils.json_loads(out) @@ -165,7 +169,7 @@ class Runner(object): # ***************************************************** @classmethod - def parse_hosts(cls, host_list, override_hosts=None): + def parse_hosts(cls, host_list, override_hosts=None, extra_vars=None): ''' parse the host inventory file, returns (hosts, groups) ''' if override_hosts is not None: @@ -183,7 +187,7 @@ class Runner(object): if not os.access(host_list, os.X_OK): return Runner.parse_hosts_from_regular_file(host_list) else: - return Runner.parse_hosts_from_script(host_list) + return Runner.parse_hosts_from_script(host_list, extra_vars) # ***************************************************** @@ -275,7 +279,12 @@ class Runner(object): ''' support per system variabes from external variable scripts, see web docs ''' host = conn.host - cmd = subprocess.Popen([Runner._external_variable_script, host], + + cmd = [Runner._external_variable_script, '--host', host] + if self.extra_vars: + cmd.extend(['--extra-vars', self.extra_vars]) + + cmd = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False @@ -384,9 +393,13 @@ class Runner(object): ''' transfer & execute a module that is not 'copy' or 'template' ''' # shell and command are the same module + # FIXME: keep these args as strings as long as possible... if module_name == 'shell': module_name = 'command' - self.module_args.append("#USE_SHELL") + if type(self.module_args) == list: + self.module_args.append("#USE_SHELL") + else: + self.module_args += " #USE_SHELL" module = self._transfer_module(conn, tmp, module_name) (result, executed) = self._execute_module(conn, tmp, module, self.module_args) @@ -406,7 +419,12 @@ class Runner(object): module_args = self.module_args if module_name == 'shell': module_name = 'command' - module_args.append("#USE_SHELL") + # FIXME: this will become cleaner once we keep args as a string + # throughout the app + if type(module_args) == list: + module_args.append("#USE_SHELL") + else: + module_args += " #USE_SHELL" async = self._transfer_module(conn, tmp, 'async_wrapper') module = self._transfer_module(conn, tmp, module_name) diff --git a/lib/ansible/utils.py b/lib/ansible/utils.py index 3883f5035e6..5cd3dff380b 100755 --- a/lib/ansible/utils.py +++ b/lib/ansible/utils.py @@ -271,7 +271,13 @@ def unquote_string(string): def parse_kv(args, unquote=True): ''' convert a string of key/value items to a dict ''' options = {} - for x in args: + # FIXME: this should be mostly unneccessary once we convert + # things to stop parsing/unparsing + if type(args) == list: + vargs = args + else: + vargs = shlex.split(args, posix=True) + for x in vargs: if x.find("=") != -1: k, v = x.split("=") if unquote: