diff --git a/lib/ansible/playbook/play.py b/lib/ansible/playbook/play.py index 6acc30ced6e..d0a3e9138ae 100644 --- a/lib/ansible/playbook/play.py +++ b/lib/ansible/playbook/play.py @@ -56,9 +56,14 @@ class Play(object): self.vars_prompt = ds.get('vars_prompt', {}) self.playbook = playbook self.vars = self._get_vars() - self.vars_files = ds.get('vars_files', []) self.basedir = basedir + self.roles = ds.get('roles', None) + + ds = self._load_roles(self.roles, ds) + self.vars_files = ds.get('vars_files', []) + self._update_vars_files_for_host(None) + self._ds = ds = utils.template(basedir, ds, self.vars) hosts = ds.get('hosts') @@ -82,12 +87,11 @@ class Play(object): self.serial = int(ds.get('serial', 0)) self.remote_port = self.remote_port self.any_errors_fatal = ds.get('any_errors_fatal', False) - self.roles = ds.get('roles', None) - load_vars = {} if self.playbook.inventory.basedir() is not None: load_vars['inventory_dir'] = self.playbook.inventory.basedir(); + self._tasks = self._load_tasks(self._ds.get('tasks', []), load_vars) self._handlers = self._load_tasks(self._ds.get('handlers', []), load_vars) @@ -100,10 +104,65 @@ class Play(object): if self.sudo_user != 'root': self.sudo = True + # ************************************************* - def _load_tasks(self, tasks, vars={}, additional_conditions=[]): + def _load_roles(self, roles, ds): + + + # a role is a name that auto-includes the following if they exist + # /tasks/main.yml + # /handlers/main.yml + # /vars/main.yml + # and it auto-extends tasks/handlers/vars_files as appropriate if found + + if roles is None: + return ds + if type(roles) != list: + raise errors.AnsibleError("value of 'roles:' must be a list") + + new_tasks = [] + new_handlers = [] + new_vars_files = [] + for orig_path in roles: + path = utils.path_dwim(self.basedir, orig_path) + if not os.path.isdir(path): + path2 = utils.path_dwim(self.basedir, os.path.join(self.basedir, 'roles', orig_path)) + if not os.path.isdir(path2): + raise errors.AnsibleError("cannot find role in %s or %s" % (path, path2)) + path = path2 + task = utils.path_dwim(self.basedir, os.path.join(path, 'tasks', 'main.yml')) + handler = utils.path_dwim(self.basedir, os.path.join(path, 'handlers', 'main.yml')) + vars_file = utils.path_dwim(self.basedir, os.path.join(path, 'vars', 'main.yml')) + if os.path.isfile(task): + new_tasks.append(dict(include=task)) + if os.path.isfile(handler): + new_handlers.append(dict(include=handler)) + if os.path.isfile(vars_file): + new_vars_files.append(vars_file) + + tasks = ds.get('tasks', None) + handlers = ds.get('handlers', None) + vars_files = ds.get('vars_files', None) + if type(tasks) != list: + tasks = [] + if type(handlers) != list: + handlers = [] + if type(vars_files) != list: + vars_files = [] + tasks.extend(new_tasks) + handlers.extend(new_handlers) + vars_files.extend(new_vars_files) + ds['tasks'] = tasks + ds['handlers'] = handlers + ds['vars_files'] = vars_files + + return ds + + # ************************************************* + + def _load_tasks(self, tasks, vars={}, additional_conditions=[], original_file=None): ''' handle task and handler include statements ''' results = [] @@ -116,6 +175,9 @@ class Play(object): raise errors.AnsibleError("expecting dict; got: %s" % x) task_vars = self.vars.copy() task_vars.update(vars) + if original_file: + task_vars['_original_file'] = original_file + if 'include' in x: tokens = shlex.split(x['include']) items = [''] @@ -147,7 +209,7 @@ class Play(object): mv[k] = utils.template(self.basedir, v, mv) include_file = utils.template(self.basedir, tokens[0], mv) data = utils.parse_yaml_from_file(utils.path_dwim(self.basedir, include_file)) - results += self._load_tasks(data, mv, included_additional_conditions) + results += self._load_tasks(data, mv, included_additional_conditions, original_file=include_file) elif type(x) == dict: results.append(Task(self,x,module_vars=task_vars, additional_conditions=additional_conditions)) else: diff --git a/lib/ansible/runner/action_plugins/copy.py b/lib/ansible/runner/action_plugins/copy.py index 6452d27e3ab..445a534dffc 100644 --- a/lib/ansible/runner/action_plugins/copy.py +++ b/lib/ansible/runner/action_plugins/copy.py @@ -56,6 +56,8 @@ class ActionModule(object): for fn in inject.get('first_available_file'): fn = utils.template(self.runner.basedir, fn, inject) fn = utils.path_dwim(self.runner.basedir, fn) + if not os.path.exists(fn) and '_original_file' in inject: + fn = utils.path_dwim_relative(inject['_original_file'], 'files', fn, self.runner.basedir, check=False) if os.path.exists(fn): source = fn found = True @@ -76,7 +78,11 @@ class ActionModule(object): source = tmp_content else: source = utils.template(self.runner.basedir, source, inject) - source = utils.path_dwim(self.runner.basedir, source) + if '_original_file' in inject: + source = utils.path_dwim_relative(inject['_original_file'], 'files', source, self.runner.basedir) + else: + source = utils.path_dwim(self.runner.basedir, source) + local_md5 = utils.md5(source) if local_md5 is None: diff --git a/lib/ansible/runner/action_plugins/template.py b/lib/ansible/runner/action_plugins/template.py index 0f7b49352c2..3ef2850f92a 100644 --- a/lib/ansible/runner/action_plugins/template.py +++ b/lib/ansible/runner/action_plugins/template.py @@ -53,10 +53,13 @@ class ActionModule(object): # look up the files and use the first one we find as src if 'first_available_file' in inject: + found = False for fn in self.runner.module_vars.get('first_available_file'): fnt = utils.template(self.runner.basedir, fn, inject) fnd = utils.path_dwim(self.runner.basedir, fnt) + if not os.path.exists(fnd) and '_original_file' in inject: + fnd = utils.path_dwim_relative(inject['_original_file'], 'templates', fnd, self.runner.basedir, check=False) if os.path.exists(fnd): source = fnt found = True @@ -66,6 +69,12 @@ class ActionModule(object): return ReturnData(conn=conn, comm_ok=False, result=result) else: source = utils.template(self.runner.basedir, source, inject) + + if '_original_file' in inject: + source = utils.path_dwim_relative(inject['_original_file'], 'templates', source, self.runner.basedir) + else: + source = utils.path_dwim(self.runner.basedir, source) + if dest.endswith("/"): base = os.path.basename(source) diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index 9f66b3f5383..bc29eb06735 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -195,11 +195,27 @@ def path_dwim(basedir, given): ''' if given.startswith("/"): - return given + return os.path.abspath(given) elif given.startswith("~"): - return os.path.expanduser(given) + return os.path.abspath(os.path.expanduser(given)) else: - return os.path.join(basedir, given) + return os.path.abspath(os.path.join(basedir, given)) + +def path_dwim_relative(original, dirname, source, playbook_base, check=True): + ''' find one file in a directory one level up in a dir named dirname relative to current ''' + # (used by roles code) + + basedir = os.path.dirname(original) + template2 = os.path.join(basedir, '..', dirname, source) + source2 = path_dwim(basedir, template2) + if os.path.exists(source2): + return source2 + obvious_local_path = path_dwim(playbook_base, source) + if os.path.exists(obvious_local_path): + return obvious_local_path + if check: + raise errors.AnsibleError("input file not found at %s or %s" % (source2, obvious_local_path)) + return source2 # which does not exist def json_loads(data): ''' parse a JSON string and return a data structure '''