diff --git a/CHANGELOG.md b/CHANGELOG.md index a73f7e3253..4069b403e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ Ansible Changes By Release ## 1.5 "Love Walks In" - Release pending! +Major features/changes: + +* when_foo which was previously deprecated is now removed, use "when:" instead. Code generates appropriate error suggestion. +* include + with_items which was previously deprecated is now removed, ditto. Use with_nested / with_together, etc. +* only_if, which is much older than when_foo and was deprecated, is similarly removed. +* ssh_alt connection plugin, much more performant than standard -c ssh, in tree, soon to replace ssh.py (in this release) + +New modules: + +* Details pending + +Misc: + * no_reboot is now defaulted to "no" in the ec2_ami module to ensure filesystem consistency in the resulting AMI. ## 1.4.3 "Could This Be Magic" - December 20, 2013 diff --git a/docsite/rst/glossary.rst b/docsite/rst/glossary.rst index 8c10086b15..352e808481 100644 --- a/docsite/rst/glossary.rst +++ b/docsite/rst/glossary.rst @@ -56,8 +56,8 @@ Conditionals ++++++++++++ A conditional is an expression that evaluates to true or false that decides whether a given task will be executed on a given -machine or not. Ansible's conditionals include 'when_boolean', -'when_string', and 'when_integer'. These are discussed in the playbook documentation. +machine or not. Ansible's conditionals are powered by the 'when' statement, and are +discussed in the playbook documentation. Diff Mode +++++++++ @@ -220,11 +220,6 @@ JSON Ansible uses JSON for return data from remote modules. This allows modules to be written in any language, not just Python. -only_if -+++++++ - -A deprecated form of the "when:" statement. It should no longer be used. - Library +++++++ diff --git a/lib/ansible/playbook/__init__.py b/lib/ansible/playbook/__init__.py index e2bd662618..189fd63d99 100644 --- a/lib/ansible/playbook/__init__.py +++ b/lib/ansible/playbook/__init__.py @@ -310,7 +310,7 @@ class PlayBook(object): remote_port=task.play.remote_port, module_vars=task.module_vars, default_vars=task.default_vars, private_key_file=self.private_key_file, setup_cache=self.SETUP_CACHE, basedir=task.play.basedir, - conditional=task.only_if, callbacks=self.runner_callbacks, + conditional=task.when, callbacks=self.runner_callbacks, sudo=task.sudo, sudo_user=task.sudo_user, transport=task.transport, sudo_pass=task.sudo_pass, is_playbook=True, check=self.check, diff=self.diff, environment=task.environment, complex_args=task.args, diff --git a/lib/ansible/playbook/play.py b/lib/ansible/playbook/play.py index cb8665dcf5..85a9378cf1 100644 --- a/lib/ansible/playbook/play.py +++ b/lib/ansible/playbook/play.py @@ -489,11 +489,11 @@ class Play(object): utils.deprecated("\"when_:\" is a removed deprecated feature, use the simplified 'when:' conditional directly", None, removed=True) elif k == 'when': if type(x[k]) is str: - included_additional_conditions.insert(0, utils.compile_when_to_only_if("jinja2_compare %s" % x[k])) + included_additional_conditions.insert(0, x[k]) elif type(x[k]) is list: for i in x[k]: - included_additional_conditions.insert(0, utils.compile_when_to_only_if("jinja2_compare %s" % i)) - elif k in ("include", "vars", "default_vars", "only_if", "sudo", "sudo_user", "role_name"): + included_additional_conditions.insert(0, i) + elif k in ("include", "vars", "default_vars", "sudo", "sudo_user", "role_name"): continue else: include_vars[k] = x[k] @@ -511,8 +511,8 @@ class Play(object): if 'vars' in x: task_vars = utils.combine_vars(task_vars, x['vars']) - if 'only_if' in x: - included_additional_conditions.append(x['only_if']) + if 'when' in x: + included_additional_conditions.append(x['when']) new_role = None if 'role_name' in x: diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py index be26c8f699..59f5c6fe1f 100644 --- a/lib/ansible/playbook/task.py +++ b/lib/ansible/playbook/task.py @@ -24,7 +24,7 @@ import sys class Task(object): __slots__ = [ - 'name', 'meta', 'action', 'only_if', 'when', 'async_seconds', 'async_poll_interval', + 'name', 'meta', 'action', 'when', 'async_seconds', 'async_poll_interval', 'notify', 'module_name', 'module_args', 'module_vars', 'default_vars', 'play', 'notified_by', 'tags', 'register', 'role_name', 'delegate_to', 'first_available_file', 'ignore_errors', @@ -35,7 +35,7 @@ class Task(object): # to prevent typos and such VALID_KEYS = [ - 'name', 'meta', 'action', 'only_if', 'async', 'poll', 'notify', + 'name', 'meta', 'action', 'when', 'async', 'poll', 'notify', 'first_available_file', 'include', 'tags', 'register', 'ignore_errors', 'delegate_to', 'local_action', 'transport', 'remote_user', 'sudo', 'sudo_user', 'sudo_pass', 'when', 'connection', 'environment', 'args', @@ -96,7 +96,6 @@ class Task(object): elif x in [ 'changed_when', 'failed_when', 'when']: if isinstance(ds[x], basestring) and ds[x].lstrip().startswith("{{"): utils.warning("It is unneccessary to use '{{' in conditionals, leave variables in loop expressions bare.") - ds[x] = "jinja2_compare %s" % (ds[x]) elif x.startswith("when_"): utils.deprecated("The 'when_' conditional has been removed. Switch to using the regular unified 'when' statements as described in ansibleworks.com/docs/.","1.5", removed=True) @@ -128,8 +127,8 @@ class Task(object): self.module_vars['delay'] = ds.get('delay', 5) self.module_vars['retries'] = ds.get('retries', 3) self.module_vars['register'] = ds.get('register', None) - self.until = "jinja2_compare %s" % (ds.get('until')) - self.module_vars['until'] = utils.compile_when_to_only_if(self.until) + self.until = ds.get('until') + self.module_vars['until'] = self.until # rather than simple key=value args on the options line, these represent structured data and the values # can be hashes and lists, not just scalars @@ -188,22 +187,10 @@ class Task(object): self.name = self.action # load various attributes - self.only_if = ds.get('only_if', 'True') - - if self.only_if != 'True': - utils.deprecated("only_if is a very old feature and has been obsolete since 0.9, please switch to the 'when' conditional as described at http://ansibleworks.com/docs","1.5",removed=True) - self.when = ds.get('when', None) self.changed_when = ds.get('changed_when', None) - - if self.changed_when is not None: - self.changed_when = utils.compile_when_to_only_if(self.changed_when) - self.failed_when = ds.get('failed_when', None) - if self.failed_when is not None: - self.failed_when = utils.compile_when_to_only_if(self.failed_when) - 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', []) @@ -276,12 +263,7 @@ class Task(object): self.tags.extend(apply_tags) self.tags.extend(import_tags) - if self.when is not None: - if self.only_if != 'True': - raise errors.AnsibleError('when obsoletes only_if, only use one or the other') - self.only_if = utils.compile_when_to_only_if(self.when) - if additional_conditions: new_conditions = additional_conditions - new_conditions.append(self.only_if) - self.only_if = new_conditions + new_conditions.append(self.when) + self.when = new_conditions diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 92b25788d0..011a31512b 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -1067,6 +1067,6 @@ class Runner(object): if self.always_run is None: self.always_run = self.module_vars.get('always_run', False) self.always_run = check_conditional( - self.always_run, self.basedir, inject, fail_on_undefined=True, jinja2=True) + self.always_run, self.basedir, inject, fail_on_undefined=True) return (self.check and not self.always_run) diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index 9a57a2ef52..0fb141946b 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -163,58 +163,44 @@ def is_changed(result): return (result.get('changed', False) in [ True, 'True', 'true']) -def check_conditional(conditional, basedir, inject, fail_on_undefined=False, jinja2=False): +def check_conditional(conditional, basedir, inject, fail_on_undefined=False): if isinstance(conditional, list): for x in conditional: - if not check_conditional(x, basedir, inject, fail_on_undefined=fail_on_undefined, jinja2=jinja2): + if not check_conditional(x, basedir, inject, fail_on_undefined=fail_on_undefined): return False return True - if jinja2: - conditional = "jinja2_compare %s" % conditional - - if conditional.startswith("jinja2_compare"): - conditional = conditional.replace("jinja2_compare ","") - # allow variable names - if conditional in inject and str(inject[conditional]).find('-') == -1: - conditional = inject[conditional] - conditional = template.template(basedir, conditional, inject, fail_on_undefined=fail_on_undefined) - original = str(conditional).replace("jinja2_compare ","") - # a Jinja2 evaluation that results in something Python can eval! - presented = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional - conditional = template.template(basedir, presented, inject) - val = conditional.strip() - if val == presented: - # the templating failed, meaning most likely a - # variable was undefined. If we happened to be - # looking for an undefined variable, return True, - # otherwise fail - if conditional.find("is undefined") != -1: - return True - elif conditional.find("is defined") != -1: - return False - else: - raise errors.AnsibleError("error while evaluating conditional: %s" % original) - elif val == "True": - return True - elif val == "False": - return False - else: - raise errors.AnsibleError("unable to evaluate conditional: %s" % original) - if not isinstance(conditional, basestring): return conditional - try: - conditional = conditional.replace("\n", "\\n") - result = safe_eval(conditional) - if result not in [ True, False ]: - raise errors.AnsibleError("Conditional expression must evaluate to True or False: %s" % conditional) - return result - - except (NameError, SyntaxError): - raise errors.AnsibleError("Could not evaluate the expression: (%s)" % conditional) + conditional = conditional.replace("jinja2_compare ","") + # allow variable names + if conditional in inject and str(inject[conditional]).find('-') == -1: + conditional = inject[conditional] + conditional = template.template(basedir, conditional, inject, fail_on_undefined=fail_on_undefined) + original = str(conditional).replace("jinja2_compare ","") + # a Jinja2 evaluation that results in something Python can eval! + presented = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional + conditional = template.template(basedir, presented, inject) + val = conditional.strip() + if val == presented: + # the templating failed, meaning most likely a + # variable was undefined. If we happened to be + # looking for an undefined variable, return True, + # otherwise fail + if conditional.find("is undefined") != -1: + return True + elif conditional.find("is defined") != -1: + return False + else: + raise errors.AnsibleError("error while evaluating conditional: %s" % original) + elif val == "True": + return True + elif val == "False": + return False + else: + raise errors.AnsibleError("unable to evaluate conditional: %s" % original) def is_executable(path): '''is the given path executable?''' @@ -756,86 +742,6 @@ def boolean(value): else: return False -def compile_when_to_only_if(expression): - ''' - when is a shorthand for writing only_if conditionals. It requires less quoting - magic. only_if is retained for backwards compatibility. - ''' - - # when: set $variable - # when: unset $variable - # when: failed $json_result - # when: changed $json_result - # when: int $x >= $z and $y < 3 - # when: int $x in $alist - # when: float $x > 2 and $y <= $z - # when: str $x != $y - # when: jinja2_compare asdf # implies {{ asdf }} - - if type(expression) not in [ str, unicode ]: - raise errors.AnsibleError("invalid usage of when_ operator: %s" % expression) - tokens = expression.split() - if len(tokens) < 2: - raise errors.AnsibleError("invalid usage of when_ operator: %s" % expression) - - # when_set / when_unset - if tokens[0] in [ 'set', 'unset' ]: - tcopy = tokens[1:] - for (i,t) in enumerate(tokens[1:]): - if t.find("$") != -1: - tcopy[i] = "is_%s('''%s''')" % (tokens[0], t) - else: - tcopy[i] = t - return " ".join(tcopy) - - # when_failed / when_changed - elif tokens[0] in [ 'failed', 'changed' ]: - tcopy = tokens[1:] - for (i,t) in enumerate(tokens[1:]): - if t.find("$") != -1: - tcopy[i] = "is_%s(%s)" % (tokens[0], t) - else: - tcopy[i] = t - return " ".join(tcopy) - - # when_integer / when_float / when_string - elif tokens[0] in [ 'integer', 'float', 'string' ]: - cast = None - if tokens[0] == 'integer': - cast = 'int' - elif tokens[0] == 'string': - cast = 'str' - elif tokens[0] == 'float': - cast = 'float' - tcopy = tokens[1:] - for (i,t) in enumerate(tokens[1:]): - #if re.search(t, r"^\w"): - # bare word will turn into Jinja2 so all the above - # casting is really not needed - #tcopy[i] = "%s('''%s''')" % (cast, t) - t2 = t.strip() - if (t2[0].isalpha() or t2[0] == '$') and cast == 'str' and t2 != 'in': - tcopy[i] = "'%s'" % (t) - else: - tcopy[i] = t - result = " ".join(tcopy) - return result - - - # when_boolean - elif tokens[0] in [ 'bool', 'boolean' ]: - tcopy = tokens[1:] - for (i, t) in enumerate(tcopy): - if t.find("$") != -1: - tcopy[i] = "(is_set('''%s''') and '''%s'''.lower() not in ('false', 'no', 'n', 'none', '0', ''))" % (t, t) - return " ".join(tcopy) - - # the stock 'when' without qualification (new in 1.2), assumes Jinja2 terms - elif tokens[0] == 'jinja2_compare': - return " ".join(tokens) - else: - raise errors.AnsibleError("invalid usage of when_ operator: %s" % expression) - def make_sudo_cmd(sudo_user, executable, cmd): """ helper function for connection plugins to create sudo commands diff --git a/test/TestUtils.py b/test/TestUtils.py index fbdbe0f405..0e7a64a481 100644 --- a/test/TestUtils.py +++ b/test/TestUtils.py @@ -112,27 +112,27 @@ class TestUtils(unittest.TestCase): # boolean assert(ansible.utils.check_conditional( - 'jinja2_compare true', '/', {}) == True) + 'true', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare false', '/', {}) == False) + 'false', '/', {}) == False) assert(ansible.utils.check_conditional( - 'jinja2_compare True', '/', {}) == True) + 'True', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare False', '/', {}) == False) + 'False', '/', {}) == False) # integer assert(ansible.utils.check_conditional( - 'jinja2_compare 1', '/', {}) == True) + '1', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare 0', '/', {}) == False) + '0', '/', {}) == False) # string, beware, a string is truthy unless empty assert(ansible.utils.check_conditional( - 'jinja2_compare "yes"', '/', {}) == True) + '"yes"', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare "no"', '/', {}) == True) + '"no"', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare ""', '/', {}) == False) + '""', '/', {}) == False) def test_check_conditional_jinja2_variable_literals(self): @@ -140,61 +140,61 @@ class TestUtils(unittest.TestCase): # boolean assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'True'}) == True) + 'var', '/', {'var': 'True'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'true'}) == True) + 'var', '/', {'var': 'true'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'False'}) == False) + 'var', '/', {'var': 'False'}) == False) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'false'}) == False) + 'var', '/', {'var': 'false'}) == False) # integer assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '1'}) == True) + 'var', '/', {'var': '1'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 1}) == True) + 'var', '/', {'var': 1}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '0'}) == False) + 'var', '/', {'var': '0'}) == False) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 0}) == False) + 'var', '/', {'var': 0}) == False) # string, beware, a string is truthy unless empty assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '"yes"'}) == True) + 'var', '/', {'var': '"yes"'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '"no"'}) == True) + 'var', '/', {'var': '"no"'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '""'}) == False) + 'var', '/', {'var': '""'}) == False) # Python boolean in Jinja2 expression assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': True}) == True) + 'var', '/', {'var': True}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': False}) == False) + 'var', '/', {'var': False}) == False) def test_check_conditional_jinja2_expression(self): assert(ansible.utils.check_conditional( - 'jinja2_compare 1 == 1', '/', {}) == True) + '1 == 1', '/', {}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare bar == 42', '/', {'bar': 42}) == True) + 'bar == 42', '/', {'bar': 42}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare bar != 42', '/', {'bar': 42}) == False) + 'bar != 42', '/', {'bar': 42}) == False) def test_check_conditional_jinja2_expression_in_variable(self): assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': '1 == 1'}) == True) + 'var', '/', {'var': '1 == 1'}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'bar == 42', 'bar': 42}) == True) + 'var', '/', {'var': 'bar == 42', 'bar': 42}) == True) assert(ansible.utils.check_conditional( - 'jinja2_compare var', '/', {'var': 'bar != 42', 'bar': 42}) == False) + 'var', '/', {'var': 'bar != 42', 'bar': 42}) == False) def test_check_conditional_jinja2_unicode(self): assert(ansible.utils.check_conditional( - u'jinja2_compare "\u00df"', '/', {}) == True) + u'"\u00df"', '/', {}) == True) assert(ansible.utils.check_conditional( - u'jinja2_compare var == "\u00df"', '/', {'var': u'\u00df'}) == True) + u'var == "\u00df"', '/', {'var': u'\u00df'}) == True) #####################################