diff --git a/examples/playbooks/intermediate_example.yml b/examples/playbooks/intermediate_example.yml index 4460603de9..665861bf16 100644 --- a/examples/playbooks/intermediate_example.yml +++ b/examples/playbooks/intermediate_example.yml @@ -4,7 +4,7 @@ # because of the comments it's less concise than it normally is. But feel # free to comment your playbooks if you like. -- hosts: dbservers +- hosts: all # we can define variables the normal way... diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py index 86868f6b26..c467ba3397 100644 --- a/lib/ansible/playbook/__init__.py +++ b/lib/ansible/playbook/__init__.py @@ -23,9 +23,7 @@ import ansible.constants as C from ansible import utils from ansible import errors import os - -# used to transfer variables to Runner -SETUP_CACHE={ } +from play import Play ############################################# @@ -82,6 +80,8 @@ class PlayBook(object): sudo: if not specified per play, requests all plays use sudo mode """ + self.SETUP_CACHE = {} + if playbook is None or callbacks is None or runner_callbacks is None or stats is None: raise Exception('missing required arguments') @@ -108,132 +108,11 @@ class PlayBook(object): self.inventory = ansible.inventory.Inventory(host_list) - if not self.inventory._is_script: self.global_vars.update(self.inventory.get_group_variables('all')) - self.basedir = os.path.dirname(playbook) - self.playbook = self._parse_playbook(playbook) - - # ***************************************************** - - def _get_vars(self, play, dirname): - ''' load the vars section from a play ''' - - if play.get('vars') is None: - play['vars'] = {} - if type(play['vars']) not in [dict, list]: - raise errors.AnsibleError("'vars' section must contain only key/value pairs") - vars = self.global_vars - - # translate a list of vars into a dict - if type(play['vars']) == list: - for item in play['vars']: - k, v = item.items()[0] - vars[k] = v - else: - vars.update(play['vars']) - - vars_prompt = play.get('vars_prompt', {}) - if type(vars_prompt) != dict: - raise errors.AnsibleError("'vars_prompt' section must contain only key/value pairs") - for vname in vars_prompt: - # TODO: make this prompt one line and consider double entry - print vars_prompt[vname] - vars[vname] = self.callbacks.on_vars_prompt(vname) - - results = self.extra_vars.copy() - results.update(vars) - return results - - # ***************************************************** - - def _include_tasks(self, play, task, dirname, new_tasks): - ''' load tasks included from external files. ''' - - # include: some.yml a=2 b=3 c=4 - play_vars = self._get_vars(play, dirname) - include_tokens = utils.template(task['include'], play_vars, SETUP_CACHE).split() - path = utils.path_dwim(dirname, include_tokens[0]) - include_vars = {} - for i,x in enumerate(include_tokens): - if x.find("=") != -1: - (k,v) = x.split("=") - include_vars[k] = v - inject_vars = play_vars.copy() - inject_vars.update(include_vars) - included = utils.parse_yaml_from_file(path) - for x in included: - if len(include_vars): - x["vars"] = include_vars - new_tasks.append(x) - - # ***************************************************** - - def _include_handlers(self, play, handler, dirname, new_handlers): - ''' load handlers from external files ''' - inject_vars = self._get_vars(play, dirname) - path = utils.template(handler['include'], inject_vars, SETUP_CACHE) - path = utils.path_dwim(dirname, path) - included = utils.template_from_file(path, inject_vars, SETUP_CACHE) - included = utils.parse_yaml(included) - for x in included: - new_handlers.append(x) - - # ***************************************************** - - def _parse_playbook(self, playbook): - ''' load YAML file, including handling for imported files ''' - - dirname = os.path.dirname(playbook) - playbook = utils.parse_yaml_from_file(playbook) - - for play in playbook: - tasks = play.get('tasks',[]) - handlers = play.get('handlers', []) - - # process tasks in this file as well as imported tasks - new_tasks = [] - for task in tasks: - if 'include' in task: - self._include_tasks(play, task, dirname, new_tasks) - else: - new_tasks.append(task) - - # now new_tasks contains a list of tasks, but tasks may contain - # lists of with_items to loop over. Do that. - # TODO: refactor into subfunction - new_tasks2 = [] - for task in new_tasks: - if 'with_items' in task: - for item in task['with_items']: - produced_task = {} - name = task.get('name', task.get('action', 'unnamed task')) - action = task.get('action', None) - only_if = task.get('only_if', None) - if action is None: - raise errors.AnsibleError('action is required') - produced_task = task.copy() - produced_task['action'] = utils.template(action, dict(item=item), SETUP_CACHE) - produced_task['name'] = utils.template(name, dict(item=item), SETUP_CACHE) - if only_if: - produced_task['only_if'] = utils.template(only_if, dict(item=item), SETUP_CACHE) - new_tasks2.append(produced_task) - else: - new_tasks2.append(task) - - play['tasks'] = new_tasks2 - - # process handlers as well as imported handlers - new_handlers = [] - for handler in handlers: - if 'include' in handler: - self._include_handlers(play, handler, dirname, new_handlers) - else: - new_handlers.append(handler) - play['handlers'] = new_handlers - - return playbook + self.basedir = os.path.dirname(playbook) + self.playbook = utils.parse_yaml_from_file(playbook) # ***************************************************** @@ -242,8 +121,8 @@ class PlayBook(object): # loop through all patterns and run them self.callbacks.on_start() - for pattern in self.playbook: - self._run_play(pattern) + for play_ds in self.playbook: + self._run_play(Play(self,play_ds)) # summarize the results results = {} @@ -269,190 +148,88 @@ class PlayBook(object): # ***************************************************** - def _run_module(self, pattern, module, args, vars, remote_user, - async_seconds, async_poll_interval, only_if, sudo, sudo_user, transport, port): + def _run_task_internal(self, task): ''' run a particular module step in a playbook ''' hosts = [ h for h in self.inventory.list_hosts() if (h not in self.stats.failures) and (h not in self.stats.dark)] self.inventory.restrict_to(hosts) - if port is None: - port=self.remote_port - runner = ansible.runner.Runner( - pattern=pattern, inventory=self.inventory, module_name=module, - module_args=args, forks=self.forks, + pattern=task.play.hosts, inventory=self.inventory, module_name=task.module_name, + module_args=task.module_args, forks=self.forks, remote_pass=self.remote_pass, module_path=self.module_path, - timeout=self.timeout, remote_user=remote_user, - remote_port=port, module_vars=vars, + timeout=self.timeout, remote_user=task.play.remote_user, + remote_port=task.play.remote_port, module_vars=task.module_vars, private_key_file=self.private_key_file, - setup_cache=SETUP_CACHE, basedir=self.basedir, - conditional=only_if, callbacks=self.runner_callbacks, - debug=self.debug, sudo=sudo, sudo_user=sudo_user, - transport=transport, sudo_pass=self.sudo_pass, is_playbook=True + setup_cache=self.SETUP_CACHE, basedir=self.basedir, + conditional=task.only_if, callbacks=self.runner_callbacks, + debug=self.debug, sudo=task.play.sudo, sudo_user=task.play.sudo_user, + transport=task.play.transport, sudo_pass=self.sudo_pass, is_playbook=True ) - if async_seconds == 0: + if task.async_seconds == 0: results = runner.run() else: - results, poller = runner.run_async(async_seconds) + results, poller = runner.run_async(task.async_seconds) self.stats.compute(results) - if async_poll_interval > 0: - # if not polling, playbook requested fire and forget - # trust the user wanted that and return immediately - results = self._async_poll(poller, async_seconds, async_poll_interval) + if task.async_poll_interval > 0: + # if not polling, playbook requested fire and forget, so don't poll + results = self._async_poll(poller, task.async_seconds, task.async_poll_interval) self.inventory.lift_restriction() return results # ***************************************************** - def _run_task(self, pattern=None, task=None, - remote_user=None, handlers=None, conditional=False, - sudo=False, sudo_user=None, transport=None, port=None): + def _run_task(self, play, task, is_handler): ''' run a single task in the playbook and recursively run any subtasks. ''' - # load the module name and parameters from the task entry - 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: 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 + self.callbacks.on_task_start(task.name, is_handler) - tokens = action.split(None, 1) - module_name = tokens[0] - module_args = '' - if len(tokens) > 1: - module_args = tokens[1] - - # include task specific vars - module_vars = task.get('vars', {}) - if 'first_available_file' in task: - module_vars['first_available_file'] = task.get('first_available_file') - - # tasks can be direct (run on all nodes matching - # the pattern) or conditional, where they ran - # as the result of a change handler on a subset - # of all of the hosts - - self.callbacks.on_task_start(name, conditional) - - # load up an appropriate ansible runner to - # run the task in parallel - results = self._run_module(pattern, module_name, - module_args, module_vars, remote_user, async_seconds, - async_poll_interval, only_if, sudo, sudo_user, transport, port) + # load up an appropriate ansible runner to run the task in parallel + results = self._run_task_internal(task) # add facts to the global setup cache for host, result in results['contacted'].iteritems(): if "ansible_facts" in result: for k,v in result['ansible_facts'].iteritems(): - SETUP_CACHE[host][k]=v + self.SETUP_CACHE[host][k]=v self.stats.compute(results) - # if no hosts are matched, carry on, unlike /bin/ansible - # which would warn you about this + # if no hosts are matched, carry on if results is None: results = {} # flag which notify handlers need to be run - # this will be on a SUBSET of the actual host list. For instance - # a file might need to be written on only half of the nodes so - # we would only trigger restarting Apache on half of the nodes - - subtasks = task.get('notify', []) - if isinstance(subtasks, basestring): - subtasks = [subtasks] - if len(subtasks) > 0: + if len(task.notify) > 0: for host, results in results.get('contacted',{}).iteritems(): if results.get('changed', False): - for subtask in subtasks: - self._flag_handler(handlers, subtask, host) + for handler_name in task.notify: + self._flag_handler(play.handlers(), handler_name, host) # ***************************************************** - def _flag_handler(self, handlers, match_name, host): + def _flag_handler(self, handlers, handler_name, host): ''' if a task has any notify elements, flag handlers for run at end of execution cycle for hosts that have indicated changes have been made ''' - # for all registered handlers in the ansible playbook - # for this particular pattern group - found = False for x in handlers: - name = x.get('name', None) - if name is None: - raise errors.AnsibleError('handler is missing a name') - if match_name == name: + if handler_name == x.name: found = True - self.callbacks.on_notify(host, name) - # flag the handler with the list of hosts it needs to be run on, it will be run later - if not 'run' in x: - x['run'] = [] - x['run'].append(host) + self.callbacks.on_notify(host, x.name) + x.notified_by.append(host) if not found: - raise errors.AnsibleError("change handler (%s) is not defined" % match_name) + raise errors.AnsibleError("change handler (%s) is not defined" % handler_name) # ***************************************************** - def _do_conditional_imports(self, vars_files, pattern=None): - ''' handle the vars_files section, which can contain variables ''' + def _do_setup_step(self, play, vars_files=None): - # FIXME: save parsed variable results in memory to avoid excessive re-reading/parsing - # FIXME: currently parses imports for hosts not in the pattern, that is not wrong, but it's - # not super optimized yet either, because we wouldn't have hit them, ergo - # it will raise false errors if there is no defaults variable file without any $vars - # in it, which could happen on uncontacted hosts. - - if type(vars_files) != list: - raise errors.AnsibleError("vars_files must be a list") - - host_list = [ h for h in self.inventory.list_hosts(pattern) - if not (h in self.stats.failures or h in self.stats.dark) ] - - for host in host_list: - cache_vars = SETUP_CACHE.get(host,{}) - SETUP_CACHE[host] = cache_vars - for filename in vars_files: - if type(filename) == list: - # loop over all filenames, loading the first one, and failing if # none found - found = False - sequence = [] - for real_filename in filename: - filename2 = utils.path_dwim(self.basedir, utils.template(real_filename, cache_vars, SETUP_CACHE)) - sequence.append(filename2) - if os.path.exists(filename2): - found = True - data = utils.parse_yaml_from_file(filename2) - SETUP_CACHE[host].update(data) - self.callbacks.on_import_for_host(host, filename2) - break - else: - self.callbacks.on_not_import_for_host(host, filename2) - if not found: - raise errors.AnsibleError( - "%s: FATAL, no files matched for vars_files import sequence: %s" % (host, sequence) - ) - - else: - filename2 = utils.path_dwim(self.basedir, utils.template(filename, cache_vars, SETUP_CACHE)) - if not os.path.exists(filename2): - raise errors.AnsibleError("no file matched for vars_file import: %s" % filename2) - data = utils.parse_yaml_from_file(filename2) - SETUP_CACHE[host].update(data) - self.callbacks.on_import_for_host(host, filename2) - - # ***************************************************** - - def _do_setup_step(self, pattern, vars, user, port, sudo, sudo_user, transport, vars_files=None): ''' push variables down to the systems and get variables+facts back up ''' # this enables conditional includes like $facter_os.yml and is only done @@ -461,27 +238,22 @@ class PlayBook(object): if vars_files is not None: self.callbacks.on_setup_secondary() - self._do_conditional_imports(vars_files, pattern) + play.update_vars_files(self.inventory.list_hosts(play.hosts)) else: self.callbacks.on_setup_primary() - host_list = [ h for h in self.inventory.list_hosts(pattern) - if not (h in self.stats.failures or h in self.stats.dark) ] + host_list = [ h for h in self.inventory.list_hosts(play.hosts) + if not (h in self.stats.failures or h in self.stats.dark) ] self.inventory.restrict_to(host_list) # push any variables down to the system setup_results = ansible.runner.Runner( - pattern=pattern, module_name='setup', - module_args=vars, inventory=self.inventory, - forks=self.forks, module_path=self.module_path, - timeout=self.timeout, remote_user=user, - remote_pass=self.remote_pass, remote_port=port, - private_key_file=self.private_key_file, - setup_cache=SETUP_CACHE, - callbacks=self.runner_callbacks, sudo=sudo, sudo_user=sudo_user, - debug=self.debug, transport=transport, - sudo_pass=self.sudo_pass, is_playbook=True + pattern=play.hosts, module_name='setup', module_args=play.vars, inventory=self.inventory, + forks=self.forks, module_path=self.module_path, timeout=self.timeout, remote_user=play.remote_user, + remote_pass=self.remote_pass, remote_port=play.remote_port, private_key_file=self.private_key_file, + setup_cache=self.SETUP_CACHE, callbacks=self.runner_callbacks, sudo=play.sudo, sudo_user=play.sudo_user, + debug=self.debug, transport=play.transport, sudo_pass=self.sudo_pass, is_playbook=True ).run() self.stats.compute(setup_results, setup=True) @@ -494,83 +266,31 @@ class PlayBook(object): # first pass only or we'll erase good work for (host, result) in setup_ok.iteritems(): if 'ansible_facts' in result: - SETUP_CACHE[host] = result['ansible_facts'] + self.SETUP_CACHE[host] = result['ansible_facts'] + return setup_results # ***************************************************** - def _run_play(self, pg): + def _run_play(self, play): ''' run a list of tasks for a given pattern, in order ''' - # get configuration information about the pattern - pattern = pg.get('hosts') - if pattern is None: - raise errors.AnsibleError('hosts declaration is required') - if isinstance(pattern, list): - pattern = ';'.join(pattern) - pattern = utils.template(pattern, self.extra_vars, {}) - name = pg.get('name', pattern) - - vars = self._get_vars(pg, self.basedir) - vars_files = pg.get('vars_files', {}) - tasks = pg.get('tasks', []) - handlers = pg.get('handlers', []) - user = pg.get('user', self.remote_user) - port = pg.get('port', self.remote_port) - sudo = pg.get('sudo', self.sudo) - sudo_user = pg.get('sudo_user', self.sudo_user) - transport = pg.get('connection', self.transport) - - # the default sudo user is root, so if you change it, sudo is implied - if sudo_user != 'root': - sudo = True - - self.callbacks.on_play_start(name) + self.callbacks.on_play_start(play.name) # push any variables down to the system # and get facts/ohai/other data back up - self._do_setup_step(pattern, vars, user, port, sudo, sudo_user, transport, None) - + rc = self._do_setup_step(play) # pattern, vars, user, port, sudo, sudo_user, transport, None) + # now with that data, handle contentional variable file imports! - if len(vars_files) > 0: - self._do_setup_step(pattern, vars, user, port, sudo, sudo_user, transport, vars_files) + if len(play.vars_files) > 0: + rc = self._do_setup_step(play, play.vars_files) # run all the top level tasks, these get run on every node - for task in tasks: - self._run_task( - pattern=pattern, - task=task, - handlers=handlers, - remote_user=user, - sudo=sudo, - sudo_user=sudo_user, - transport=transport, - port=port - ) + for task in play.tasks(): + self._run_task(play, task, False) - # handlers only run on certain nodes, they are flagged by _flag_handlers - # above. They only run on nodes when things mark them as changed, and - # handlers only get run once. For instance, the system is designed - # such that multiple config files if changed can ask for an Apache restart - # but Apache will only be restarted once (at the end). - - for task in handlers: - triggered_by = task.get('run', None) - if type(triggered_by) == list: - self.inventory.restrict_to(triggered_by) - self._run_task( - pattern=pattern, - task=task, - handlers=[], - conditional=True, - remote_user=user, - sudo=sudo, - sudo_user=sudo_user, - transport=transport, - port=port - ) + # run notify actions + for handler in play.handlers(): + if len(handler.notified_by) > 0: + self.inventory.restrict_to(handler.notified_by) + self._run_task(play, handler, True) self.inventory.lift_restriction() - # end of execution for this particular pattern. Multiple patterns - # can be in a single playbook file - - - diff --git a/lib/ansible/playbook/play.py b/lib/ansible/playbook/play.py new file mode 100644 index 0000000000..7795038f96 --- /dev/null +++ b/lib/ansible/playbook/play.py @@ -0,0 +1,176 @@ +# (c) 2012, Michael DeHaan +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +############################################# + +from ansible import utils +from ansible import errors +from ansible.playbook.task import Task +import shlex +import os + +class Play(object): + + __slots__ = [ + 'hosts', 'name', 'vars', 'vars_prompt', 'vars_files', 'handlers', 'remote_user', 'remote_port', + 'sudo', 'sudo_user', 'transport', 'playbook', '_ds', '_handlers', '_tasks' + ] + + # ************************************************* + + def __init__(self, playbook, ds): + + self._ds = ds + self.playbook = playbook + self.hosts = ds.get('hosts', None) + self.hosts = utils.template(self.hosts, self.playbook.extra_vars, {}) + self.name = ds.get('name', self.hosts) + self.vars = ds.get('vars', {}) + self.vars_files = ds.get('vars_files', []) + self.vars_prompt = ds.get('vars_prompt', {}) + self.vars = self._get_vars(self.playbook.basedir) + self._tasks = ds.get('tasks', []) + self._handlers = ds.get('handlers', []) + self.remote_user = ds.get('user', self.playbook.remote_user) + self.remote_port = ds.get('port', self.playbook.remote_port) + self.sudo = ds.get('sudo', self.playbook.sudo) + self.sudo_user = ds.get('sudo_user', self.playbook.sudo_user) + self.transport = ds.get('connection', self.playbook.transport) + self._tasks = self._load_tasks(self._ds, 'tasks') + self._handlers = self._load_tasks(self._ds, 'handlers') + + if self.hosts is None: + raise errors.AnsibleError('hosts declaration is required') + if isinstance(self.hosts, list): + self.hosts = ';'.join(self.hosts) + if self.sudo_user != 'root': + self.sudo = True + + # ************************************************* + + def _load_tasks(self, ds, keyname): + ''' handle task and handler include statements ''' + + items = ds.get(keyname, []) + results = [] + for x in items: + if 'include' in x: + task_vars = self.vars.copy() + tokens = shlex.split(x['include']) + for t in tokens[1:]: + (k,v) = t.split("=", 1) + task_vars[k]=v + include_file = tokens[0] + data = utils.parse_yaml_from_file(utils.path_dwim(self.playbook.basedir, include_file)) + for y in data: + t = Task(self,y) + # TODO: rename this to just 'vars' + t.module_vars = self.vars.copy() + t.module_vars.update(task_vars) + results.append(t) + elif type(x) == dict: + results.append(Task(self, x)) + else: + raise Exception("unexpected task type") + return results + + # ************************************************* + + def handlers(self): + return self._handlers + + def tasks(self): + return self._tasks + + # ************************************************* + + def _get_vars(self, dirname): + ''' load the vars section from a play ''' + + if self.vars is None: + self.vars = {} + + if type(self.vars) not in [dict, list]: + raise errors.AnsibleError("'vars' section must contain only key/value pairs") + + vars = self.playbook.global_vars + + # translate a list of vars into a dict + if type(self.vars) == list: + for item in self.vars: + k, v = item.items()[0] + vars[k] = v + else: + vars.update(self.vars) + + if type(self.vars_prompt) != dict: + raise errors.AnsibleError("'vars_prompt' section must contain only key/value pairs") + for vname in self.vars_prompt: + # TODO: make this prompt one line and consider double entry + vars[vname] = self.callbacks.on_vars_prompt(vname) + + results = self.playbook.extra_vars.copy() + results.update(vars) + return results + + # ************************************************* + + def update_vars_files(self, hosts): + ''' calculate vars_files, which requires that setup runs first so ansible facts can be mixed in ''' + for h in hosts: + self.update_vars_files_for_host(h) + + # ************************************************* + + def update_vars_files_for_host(self, host): + + if not host in self.playbook.SETUP_CACHE: + # no need to process failed hosts or hosts not in this play + return + + for filename in self.vars_files: + # TODO: maybe have to template the path here... + + if type(filename) == list: + + # loop over all filenames, loading the first one, and failing if # none found + found = False + sequence = [] + for real_filename in filename: + filename2 = utils.template(real_filename, self.playbook.SETUP_CACHE[host]) + filename2 = utils.template(filename2, self.vars) + filename2 = utils.path_dwim(self.playbook.basedir, filename2) + sequence.append(filename2) + if os.path.exists(filename2): + found = True + data = utils.parse_yaml_from_file(filename2) + self.playbook.SETUP_CACHE[host].update(data) + self.playbook.callbacks.on_import_for_host(host, filename2) + break + else: + self.playbook.callbacks.on_not_import_for_host(host, filename2) + if not found: + raise errors.AnsibleError( + "%s: FATAL, no files matched for vars_files import sequence: %s" % (host, sequence) + ) + + else: + + fpath = utils.path_dwim(self.playbook.basedir, utils.template(filename, self.vars)) + new_vars = utils.parse_yaml_from_file(fpath) + self.playbook.SETUP_CACHE[host].update(new_vars) + diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py new file mode 100644 index 0000000000..9983d13ab1 --- /dev/null +++ b/lib/ansible/playbook/task.py @@ -0,0 +1,69 @@ +# (c) 2012, Michael DeHaan +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +############################################# + +#import ansible.inventory +#import ansible.runner +#import ansible.constants as C +#from ansible import utils +#from ansible import errors + +class Task(object): + + __slots__ = [ + 'name', 'action', 'only_if', 'async_seconds', 'async_poll_interval', + 'notify', 'module_name', 'module_args', 'module_vars', 'play', 'notified_by', + ] + + def __init__(self, play, ds): + + self.play = play + + # FIXME: error handling on invalid fields + # action... + + self.name = ds.get('name', None) + self.action = ds.get('action', '') + self.notified_by = [] + + if self.name is None: + self.name = self.action + + self.only_if = ds.get('only_if', 'True') + self.async_seconds = int(ds.get('async', 0)) # not async by default + self.async_poll_interval = int(ds.get('poll', 10)) # default poll = 10 seconds + self.notify = ds.get('notify', []) + if isinstance(self.notify, basestring): + self.notify = [ self.notify ] + + tokens = self.action.split(None, 1) + if len(tokens) < 1: + # FIXME: better error handling + raise Exception("invalid action in task: %s" % ds) + + self.module_name = tokens[0] + self.module_args = '' + if len(tokens) > 1: + self.module_args = tokens[1] + + # include task specific vars + self.module_vars = ds.get('vars', {}) + if 'first_available_file' in ds: + self.module_vars['first_available_file'] = ds.get('first_available_file') + + diff --git a/lib/ansible/utils.py b/lib/ansible/utils.py index 8373a82fc4..c25d93c245 100644 --- a/lib/ansible/utils.py +++ b/lib/ansible/utils.py @@ -254,7 +254,7 @@ def varReplace(raw, vars): return ''.join(done) -def template(text, vars, setup_cache, no_engine=True): +def template(text, vars, setup_cache=None, no_engine=True): ''' run a text buffer through the templating engine ''' vars = vars.copy() vars['hostvars'] = setup_cache