From 2eda9a3a47731961ae4797036cae8613a04667b8 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Fri, 10 Oct 2014 12:23:58 -0500 Subject: [PATCH] Fixing item loop when undefined variable errors occur because of missing attributes Fixes a case where the variable 'foo' may exist, but the with_items loop was used on something like 'foo.results', where 'results' was not a valid attribute of 'foo'. Prior to this patch, conditionals were not evaluated until later, meaning there was no opportunity to allow a test to skip the task or item based on it being undefined. --- lib/ansible/runner/__init__.py | 21 +++++++++++++--- .../roles/test_conditionals/tasks/main.yml | 25 +++++++++++++++++++ .../roles/test_conditionals/vars/main.yml | 13 ++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 test/integration/roles/test_conditionals/vars/main.yml diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 41e5d6054da..75a8a0766a0 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -663,9 +663,24 @@ class Runner(object): if os.path.exists(filesdir): basedir = filesdir - items_terms = self.module_vars.get('items_lookup_terms', '') - items_terms = template.template(basedir, items_terms, inject) - items = utils.plugins.lookup_loader.get(items_plugin, runner=self, basedir=basedir).run(items_terms, inject=inject) + try: + items_terms = self.module_vars.get('items_lookup_terms', '') + items_terms = template.template(basedir, items_terms, inject) + items = utils.plugins.lookup_loader.get(items_plugin, runner=self, basedir=basedir).run(items_terms, inject=inject) + except errors.AnsibleUndefinedVariable, e: + if 'has no attribute' in str(e): + # the undefined variable was an attribute of a variable that does + # exist, so try and run this through the conditional check to see + # if the user wanted to skip something on being undefined + if utils.check_conditional(self.conditional, self.basedir, inject, fail_on_undefined=True): + # the conditional check passed, so we have to fail here + raise + else: + # the conditional failed, so we skip this task + result = utils.jsonify(dict(changed=False, skipped=True)) + self.callbacks.on_skipped(host, None) + return ReturnData(host=host, result=result) + # strip out any jinja2 template syntax within # the data returned by the lookup plugin items = utils._clean_data_struct(items, from_remote=True) diff --git a/test/integration/roles/test_conditionals/tasks/main.yml b/test/integration/roles/test_conditionals/tasks/main.yml index f2aa0068c60..136e9501ea7 100644 --- a/test/integration/roles/test_conditionals/tasks/main.yml +++ b/test/integration/roles/test_conditionals/tasks/main.yml @@ -267,3 +267,28 @@ that: - "result.changed" +- name: test a with_items loop using a variable with a missing attribute + debug: var=item + with_items: foo.results + when: foo is defined and 'results' in foo + register: result + +- name: assert the task was skipped + assert: + that: + - "'skipped' in result" + - result.skipped + +- name: test a with_items loop skipping a single item + debug: var=item + with_items: items.results + when: item != 'b' + register: result + +- debug: var=result + +- name: assert only a single item was skipped + assert: + that: + - result.results|length == 3 + - result.results[1].skipped diff --git a/test/integration/roles/test_conditionals/vars/main.yml b/test/integration/roles/test_conditionals/vars/main.yml new file mode 100644 index 00000000000..dddcfc59983 --- /dev/null +++ b/test/integration/roles/test_conditionals/vars/main.yml @@ -0,0 +1,13 @@ +--- +# foo is a dictionary that will be used to check that +# a conditional passes a with_items loop on a variable +# with a missing attribute (ie. foo.results) +foo: + bar: a + +items: + results: + - a + - b + - c +