diff -Nru ansible-2.1.1.0/CHANGELOG.md ansible-2.1.2.0/CHANGELOG.md --- ansible-2.1.1.0/CHANGELOG.md 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/CHANGELOG.md 2016-09-29 16:06:33.000000000 +0000 @@ -1,7 +1,67 @@ Ansible Changes By Release ========================== -## 2.1.2 "The Song Remains the Same" - ACTIVE DEVELOPMENT +## 2.1.2 "The Song Remains the Same" - 09-29-2016 + +###Minor Changes: +* Fixed a bug related to creation of retry files (#17456) +* Fixed a bug in the way include params are used when an include task is dynamic (#17064) +* Fixed a bug related to including blocks in an include task (#15963) +* Fixed a bug related to the use of hostvars internally when creating the connection plugin. This prevents things like variables using lookups from being evaluated unnecessarily (#17024) +* Fixed a bug where using a variable containing a list for the `hosts` of a play resulted in an list of lists (#16583) +* Fixed a bug where integer values would cause an error if a module param was of type `float` (no issue) +* Fixed a bug with net_template failing if src was not specified (#17726) +* Fixed a bug in "ansible-galaxy import" (#17417) +* Fixed a bug in which INI files incorrectly treated a hosts range as a section header (#15331) +* Fixed a bug in which the max_fail_percentage calculation erroneously caused a series of plays to stop executing (#15954) +* Fixed a bug in which the task names were not properly templated (#16295) +* Fixed a bug causing "squashed" loops (ie. yum, apt) to incorrectly report results (ansible-modules-core#4214) +* Fixed several bugs related to includes: + - when including statically, make sure that all parents were also included statically (issue #16990) + - properly resolve nested static include paths + - print a message when a file is statically included +* Fixed a bug in which module params expected to be float types were not converted from integers (only strings) (#17325) +* Fixed a bug introduced by static includes in 2.1, which prevented notifications from going to the "top level" handler name. +* Fixed a bug where a group_vars or host_vars directory in the current working directory would be used (and would take precedence) over those in the inventory and/or playbook directory. +* Fixed a bug which could occur when the result of an async task did not parse as valid JSON. +* (re)-allowed the use of ansible_python_interpreter lines with more than one argument. +* Fixed several bugs related to the creation of the implicit localhost in inventory. +* Fixed a bug related to an unspecified number of retries when using until. +* Fixed a race-condition bug when creating temp directories before the worker process is forked. +* Fix a bug with async's poll keyword not making use of ansible_python_interpreter to run (and thus breaking when /usr/bin/python is not present on the remote machine.) +* Fix a bug where hosts that started with a range in inventory were being treated as an invalid section header. + +Module fixes: +* Fixed a bug where the temporary CA files created by the module helper code were not being deleted properly in some situations (#17073) +* Fixed many bugs in the unarchive module +* Fixes for module ec2: + - Fixed a bug related to source_dest_check when used with non-vpc instances (core#3243) + - Fixed a bug in ec2 where instances were not powering of when referenced via tags only (core#4765) + - Fixed a bug where instances with multiple interfaces were not powering up/down correctly (core#3234) +* Fixes for module get_url: + - Fixed a bug in get_url module to force a download if there is a checksum mismatch regardless of the last modified time (core#4262) + - Fixed a bug in get_url module to properly process FTP results (core#3661 and core#4601) +* Fixed a bug in win_user related to users with disabled accounts/expired passwords (core#4369) +* ini_file: + - Fixed a bug where option lines are now inserted before blank lines. + - Fixed a bug where leading whitespace prevented matches on options. +* Fixed a bug in iam_cert when dup_ok is used as a string. +* Fixed a bug in postgresql_db related to the changed logic when state=absent. +* Fixed a bug where single_transaction and quick were not passed into db_dump for the mysql_db module. +* Fixed a bug where the fetch module was not idempotent when retrieving the target of a symlink. +* Many minor fixes for bugs in extras modules. + +###Deprecations: + +* Deprecated the use of `_fixup_perms`. Use `_fixup_perms2` instead. + This change only impacts custom action plugins using `_fixup_perms`. + +###Incompatible Changes: + +* Use of `_fixup_perms` with `recursive=True` (the default) is no longer supported. + Custom action plugins using `_fixup_perms` will require changes unless they already use `recursive=False`. + Use `_fixup_perms2` if support for previous releases is not required. + Otherwise use `_fixup_perms` with `recursive=False`. ## 2.1.1 "The Song Remains the Same" - 07-28-2016 diff -Nru ansible-2.1.1.0/debian/changelog ansible-2.1.2.0/debian/changelog --- ansible-2.1.1.0/debian/changelog 2016-07-28 17:54:52.000000000 +0000 +++ ansible-2.1.2.0/debian/changelog 2016-09-29 16:06:47.000000000 +0000 @@ -1,8 +1,15 @@ -ansible (2.1.1.0-1ppa~precise) precise; urgency=low +ansible (2.1.2.0-1ppa~precise) precise; urgency=low - * 2.1.1.0 release + * 2.1.2.0 release + + -- Ansible, Inc. Thu, 29 Sep 2016 12:06:39 -0400 + +ansible (2.1.2.0) unstable; urgency=low + + * 2.1.2.0 + + -- Ansible, Inc. Thu, 29 Sep 2016 10:01:34 -0500 - -- Ansible, Inc. Thu, 28 Jul 2016 13:54:44 -0400 ansible (2.1.1.0) unstable; urgency=low diff -Nru ansible-2.1.1.0/debian/changeloge ansible-2.1.2.0/debian/changeloge --- ansible-2.1.1.0/debian/changeloge 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/debian/changeloge 2016-09-29 16:06:33.000000000 +0000 @@ -4,6 +4,13 @@ -- Ansible, Inc. %DATE% +ansible (2.1.2.0) unstable; urgency=low + + * 2.1.2.0 + + -- Ansible, Inc. Thu, 29 Sep 2016 10:01:34 -0500 + + ansible (2.1.1.0) unstable; urgency=low * 2.1.1.0 diff -Nru ansible-2.1.1.0/docs/man/man1/ansible.1 ansible-2.1.2.0/docs/man/man1/ansible.1 --- ansible-2.1.1.0/docs/man/man1/ansible.1 2016-07-28 17:54:45.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible.1 2016-09-29 16:06:40.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible .\" Author: :doctype:manpage .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 07/28/2016 +.\" Date: 09/29/2016 .\" Manual: System administration commands -.\" Source: Ansible 2.1.1.0 +.\" Source: Ansible 2.1.2.0 .\" Language: English .\" -.TH "ANSIBLE" "1" "07/28/2016" "Ansible 2\&.1\&.1\&.0" "System administration commands" +.TH "ANSIBLE" "1" "09/29/2016" "Ansible 2\&.1\&.2\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.1.1.0/docs/man/man1/ansible.1.asciidoc ansible-2.1.2.0/docs/man/man1/ansible.1.asciidoc --- ansible-2.1.1.0/docs/man/man1/ansible.1.asciidoc 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible.1.asciidoc 2016-09-29 16:06:39.000000000 +0000 @@ -2,7 +2,7 @@ ========= :doctype:manpage :man source: Ansible -:man version: 2.1.1.0 +:man version: 2.1.2.0 :man manual: System administration commands NAME diff -Nru ansible-2.1.1.0/docs/man/man1/ansible-doc.1 ansible-2.1.2.0/docs/man/man1/ansible-doc.1 --- ansible-2.1.1.0/docs/man/man1/ansible-doc.1 2016-07-28 17:54:47.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible-doc.1 2016-09-29 16:06:43.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-doc .\" Author: :doctype:manpage .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 07/28/2016 +.\" Date: 09/29/2016 .\" Manual: System administration commands -.\" Source: Ansible 2.1.1.0 +.\" Source: Ansible 2.1.2.0 .\" Language: English .\" -.TH "ANSIBLE\-DOC" "1" "07/28/2016" "Ansible 2\&.1\&.1\&.0" "System administration commands" +.TH "ANSIBLE\-DOC" "1" "09/29/2016" "Ansible 2\&.1\&.2\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.1.1.0/docs/man/man1/ansible-doc.1.asciidoc ansible-2.1.2.0/docs/man/man1/ansible-doc.1.asciidoc --- ansible-2.1.1.0/docs/man/man1/ansible-doc.1.asciidoc 2016-07-28 17:54:47.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible-doc.1.asciidoc 2016-09-29 16:06:43.000000000 +0000 @@ -2,7 +2,7 @@ ============== :doctype:manpage :man source: Ansible -:man version: 2.1.1.0 +:man version: 2.1.2.0 :man manual: System administration commands NAME diff -Nru ansible-2.1.1.0/docs/man/man1/ansible-galaxy.1 ansible-2.1.2.0/docs/man/man1/ansible-galaxy.1 --- ansible-2.1.1.0/docs/man/man1/ansible-galaxy.1 2016-07-28 17:54:48.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible-galaxy.1 2016-09-29 16:06:44.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-galaxy .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 07/28/2016 +.\" Date: 09/29/2016 .\" Manual: System administration commands -.\" Source: Ansible 2.1.1.0 +.\" Source: Ansible 2.1.2.0 .\" Language: English .\" -.TH "ANSIBLE\-GALAXY" "1" "07/28/2016" "Ansible 2\&.1\&.1\&.0" "System administration commands" +.TH "ANSIBLE\-GALAXY" "1" "09/29/2016" "Ansible 2\&.1\&.2\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.1.1.0/docs/man/man1/ansible-galaxy.1.asciidoc ansible-2.1.2.0/docs/man/man1/ansible-galaxy.1.asciidoc --- ansible-2.1.1.0/docs/man/man1/ansible-galaxy.1.asciidoc 2016-07-28 17:54:47.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible-galaxy.1.asciidoc 2016-09-29 16:06:43.000000000 +0000 @@ -2,7 +2,7 @@ =================== :doctype: manpage :man source: Ansible -:man version: 2.1.1.0 +:man version: 2.1.2.0 :man manual: System administration commands NAME diff -Nru ansible-2.1.1.0/docs/man/man1/ansible-playbook.1 ansible-2.1.2.0/docs/man/man1/ansible-playbook.1 --- ansible-2.1.1.0/docs/man/man1/ansible-playbook.1 2016-07-28 17:54:46.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible-playbook.1 2016-09-29 16:06:41.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-playbook .\" Author: :doctype:manpage .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 07/28/2016 +.\" Date: 09/29/2016 .\" Manual: System administration commands -.\" Source: Ansible 2.1.1.0 +.\" Source: Ansible 2.1.2.0 .\" Language: English .\" -.TH "ANSIBLE\-PLAYBOOK" "1" "07/28/2016" "Ansible 2\&.1\&.1\&.0" "System administration commands" +.TH "ANSIBLE\-PLAYBOOK" "1" "09/29/2016" "Ansible 2\&.1\&.2\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.1.1.0/docs/man/man1/ansible-playbook.1.asciidoc ansible-2.1.2.0/docs/man/man1/ansible-playbook.1.asciidoc --- ansible-2.1.1.0/docs/man/man1/ansible-playbook.1.asciidoc 2016-07-28 17:54:45.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible-playbook.1.asciidoc 2016-09-29 16:06:40.000000000 +0000 @@ -2,7 +2,7 @@ =================== :doctype:manpage :man source: Ansible -:man version: 2.1.1.0 +:man version: 2.1.2.0 :man manual: System administration commands NAME diff -Nru ansible-2.1.1.0/docs/man/man1/ansible-pull.1 ansible-2.1.2.0/docs/man/man1/ansible-pull.1 --- ansible-2.1.1.0/docs/man/man1/ansible-pull.1 2016-07-28 17:54:47.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible-pull.1 2016-09-29 16:06:42.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible .\" Author: :doctype:manpage .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 07/28/2016 +.\" Date: 09/29/2016 .\" Manual: System administration commands -.\" Source: Ansible 2.1.1.0 +.\" Source: Ansible 2.1.2.0 .\" Language: English .\" -.TH "ANSIBLE" "1" "07/28/2016" "Ansible 2\&.1\&.1\&.0" "System administration commands" +.TH "ANSIBLE" "1" "09/29/2016" "Ansible 2\&.1\&.2\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.1.1.0/docs/man/man1/ansible-pull.1.asciidoc ansible-2.1.2.0/docs/man/man1/ansible-pull.1.asciidoc --- ansible-2.1.1.0/docs/man/man1/ansible-pull.1.asciidoc 2016-07-28 17:54:46.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible-pull.1.asciidoc 2016-09-29 16:06:42.000000000 +0000 @@ -2,7 +2,7 @@ ========= :doctype:manpage :man source: Ansible -:man version: 2.1.1.0 +:man version: 2.1.2.0 :man manual: System administration commands diff -Nru ansible-2.1.1.0/docs/man/man1/ansible-vault.1 ansible-2.1.2.0/docs/man/man1/ansible-vault.1 --- ansible-2.1.1.0/docs/man/man1/ansible-vault.1 2016-07-28 17:54:49.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible-vault.1 2016-09-29 16:06:45.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-vault .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 07/28/2016 +.\" Date: 09/29/2016 .\" Manual: System administration commands -.\" Source: Ansible 2.1.1.0 +.\" Source: Ansible 2.1.2.0 .\" Language: English .\" -.TH "ANSIBLE\-VAULT" "1" "07/28/2016" "Ansible 2\&.1\&.1\&.0" "System administration commands" +.TH "ANSIBLE\-VAULT" "1" "09/29/2016" "Ansible 2\&.1\&.2\&.0" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.1.1.0/docs/man/man1/ansible-vault.1.asciidoc ansible-2.1.2.0/docs/man/man1/ansible-vault.1.asciidoc --- ansible-2.1.1.0/docs/man/man1/ansible-vault.1.asciidoc 2016-07-28 17:54:48.000000000 +0000 +++ ansible-2.1.2.0/docs/man/man1/ansible-vault.1.asciidoc 2016-09-29 16:06:44.000000000 +0000 @@ -2,7 +2,7 @@ ================ :doctype: manpage :man source: Ansible -:man version: 2.1.1.0 +:man version: 2.1.2.0 :man manual: System administration commands NAME diff -Nru ansible-2.1.1.0/lib/ansible/constants.py ansible-2.1.2.0/lib/ansible/constants.py --- ansible-2.1.1.0/lib/ansible/constants.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/constants.py 2016-09-29 16:06:33.000000000 +0000 @@ -28,6 +28,7 @@ from ansible.parsing.quoting import unquote from ansible.errors import AnsibleOptionsError +from ansible.utils.path import makedirs_safe # copied from utils, avoid circular reference fun :) def mk_boolean(value): @@ -75,8 +76,9 @@ elif istmppath: value = shell_expand(value) if not os.path.exists(value): - os.makedirs(value, 0o700) - value = tempfile.mkdtemp(prefix='ansible-local-tmp', dir=value) + makedirs_safe(value, 0o700) + prefix = 'ansible-local-%s' % os.getpid() + value = tempfile.mkdtemp(prefix=prefix, dir=value) elif ispathlist: if isinstance(value, string_types): value = [shell_expand(x, expand_relative_paths=expand_relative_paths) \ diff -Nru ansible-2.1.1.0/lib/ansible/executor/module_common.py ansible-2.1.2.0/lib/ansible/executor/module_common.py --- ansible-2.1.1.0/lib/ansible/executor/module_common.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/executor/module_common.py 2016-09-29 16:06:33.000000000 +0000 @@ -626,15 +626,10 @@ if shebang is None: shebang = u'#!/usr/bin/python' - executable = interpreter.split(u' ', 1) - if len(executable) == 2 and executable[0].endswith(u'env'): - # Handle /usr/bin/env python style interpreter settings - interpreter = u"'{0}', '{1}'".format(*executable) - else: - # Still have to enclose the parts of the interpreter in quotes - # because we're substituting it into the template as a python - # string - interpreter = u"'{0}'".format(interpreter) + # Enclose the parts of the interpreter in quotes because we're + # substituting it into the template as a Python string + interpreter_parts = interpreter.split(u' ') + interpreter = u"'{0}'".format(u"', '".join(interpreter_parts)) output.write(to_bytes(ACTIVE_ZIPLOADER_TEMPLATE % dict( zipdata=zipdata, diff -Nru ansible-2.1.1.0/lib/ansible/executor/playbook_executor.py ansible-2.1.2.0/lib/ansible/executor/playbook_executor.py --- ansible-2.1.1.0/lib/ansible/executor/playbook_executor.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/executor/playbook_executor.py 2016-09-29 16:06:33.000000000 +0000 @@ -71,7 +71,7 @@ try: for playbook_path in self._playbooks: pb = Playbook.load(playbook_path, variable_manager=self._variable_manager, loader=self._loader) - self._inventory.set_playbook_basedir(os.path.dirname(playbook_path)) + self._inventory.set_playbook_basedir(os.path.realpath(os.path.dirname(playbook_path))) if self._tqm is None: # we are doing a listing entry = {'playbook': playbook_path} @@ -156,22 +156,19 @@ # batch failed failed_hosts_count = len(self._tqm._failed_hosts) + len(self._tqm._unreachable_hosts) - \ (previously_failed + previously_unreachable) - if new_play.max_fail_percentage is not None and \ - int((new_play.max_fail_percentage)/100.0 * len(batch)) > int((len(batch) - failed_hosts_count) / len(batch) * 100.0): - break_play = True - break - elif len(batch) == failed_hosts_count: + + if len(batch) == failed_hosts_count: break_play = True break + # update the previous counts so they don't accumulate incorrectly + # over multiple serial batches + previously_failed += len(self._tqm._failed_hosts) - previously_failed + previously_unreachable += len(self._tqm._unreachable_hosts) - previously_unreachable + # save the unreachable hosts from this batch self._unreachable_hosts.update(self._tqm._unreachable_hosts) - # if the last result wasn't zero or 3 (some hosts were unreachable), - # break out of the serial batch loop - if result not in (self._tqm.RUN_OK, self._tqm.RUN_UNREACHABLE_HOSTS): - break - if break_play: break @@ -190,7 +187,7 @@ if C.RETRY_FILES_SAVE_PATH: basedir = C.shell_expand(C.RETRY_FILES_SAVE_PATH) elif playbook_path: - basedir = os.path.dirname(playbook_path) + basedir = os.path.dirname(os.path.abspath(playbook_path)) else: basedir = '~/' diff -Nru ansible-2.1.1.0/lib/ansible/executor/play_iterator.py ansible-2.1.2.0/lib/ansible/executor/play_iterator.py --- ansible-2.1.1.0/lib/ansible/executor/play_iterator.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/executor/play_iterator.py 2016-09-29 16:06:33.000000000 +0000 @@ -480,6 +480,8 @@ elif state.fail_state != self.FAILED_NONE: if state.run_state == self.ITERATING_RESCUE and state.fail_state&self.FAILED_RESCUE == 0: return False + elif state.run_state == self.ITERATING_ALWAYS and state.fail_state&self.FAILED_ALWAYS == 0: + return False else: return True elif state.run_state == self.ITERATING_TASKS and self._check_failed_state(state.tasks_child_state): diff -Nru ansible-2.1.1.0/lib/ansible/executor/process/result.py ansible-2.1.2.0/lib/ansible/executor/process/result.py --- ansible-2.1.1.0/lib/ansible/executor/process/result.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/executor/process/result.py 2016-09-29 16:06:33.000000000 +0000 @@ -173,7 +173,7 @@ # if this task is registering facts, do that now loop_var = 'item' if result._task.loop_control: - loop_var = result._task.loop_control.get('loop_var') or 'item' + loop_var = result._task.loop_control.loop_var or 'item' item = result_item.get(loop_var, None) if result._task.action == 'include_vars': for (key, value) in iteritems(result_item['ansible_facts']): diff -Nru ansible-2.1.1.0/lib/ansible/executor/task_executor.py ansible-2.1.2.0/lib/ansible/executor/task_executor.py --- ansible-2.1.1.0/lib/ansible/executor/task_executor.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/executor/task_executor.py 2016-09-29 16:06:33.000000000 +0000 @@ -409,7 +409,17 @@ # get the connection and the handler for this execution if not self._connection or not getattr(self._connection, 'connected', False) or self._play_context.remote_addr != self._connection._play_context.remote_addr: self._connection = self._get_connection(variables=variables, templar=templar) - self._connection.set_host_overrides(host=self._host, hostvars=variables.get('hostvars', {}).get(self._host.name, {})) + hostvars = variables.get('hostvars', None) + if hostvars: + try: + target_hostvars = hostvars.raw_get(self._host.name) + except: + # FIXME: this should catch the j2undefined error here + # specifically instead of all exceptions + target_hostvars = dict() + else: + target_hostvars = dict() + self._connection.set_host_overrides(host=self._host, hostvars=target_hostvars) else: # if connection is reused, its _play_context is no longer valid and needs # to be replaced with the one templated above, in case other data changed @@ -424,11 +434,13 @@ # Read some values from the task, so that we can modify them if need be if self._task.until: - retries = self._task.retries + 1 + retries = self._task.retries if retries is None: retries = 3 elif retries <= 0: retries = 1 + else: + retries += 1 else: retries = 1 @@ -460,7 +472,7 @@ if self._task.async > 0: if self._task.poll > 0: - result = self._poll_async_result(result=result, templar=templar) + result = self._poll_async_result(result=result, templar=templar, task_vars=vars_copy) # ensure no log is preserved result["_ansible_no_log"] = self._play_context.no_log @@ -544,11 +556,14 @@ display.debug("attempt loop complete, returning result") return result - def _poll_async_result(self, result, templar): + def _poll_async_result(self, result, templar, task_vars=None): ''' Polls for the specified JID to be complete ''' + if task_vars is None: + task_vars = self._job_vars + async_jid = result.get('ansible_job_id') if async_jid is None: return dict(failed=True, msg="No job id was returned by the async task") @@ -576,19 +591,19 @@ while time_left > 0: time.sleep(self._task.poll) - async_result = normal_handler.run() + async_result = normal_handler.run(task_vars=task_vars) # We do not bail out of the loop in cases where the failure # is associated with a parsing error. The async_runner can # have issues which result in a half-written/unparseable result # file on disk, which manifests to the user as a timeout happening # before it's time to timeout. - if int(async_result.get('finished', 0)) == 1 or ('failed' in async_result and async_result.get('parsed', True)) or 'skipped' in async_result: + if int(async_result.get('finished', 0)) == 1 or ('failed' in async_result and async_result.get('_ansible_parsed', False)) or 'skipped' in async_result: break time_left -= self._task.poll if int(async_result.get('finished', 0)) != 1: - if async_result.get('parsed'): + if async_result.get('_ansible_parsed'): return dict(failed=True, msg="async task did not complete within the requested time") else: return dict(failed=True, msg="async task produced unparseable results", async_result=async_result) diff -Nru ansible-2.1.1.0/lib/ansible/executor/task_result.py ansible-2.1.2.0/lib/ansible/executor/task_result.py --- ansible-2.1.1.0/lib/ansible/executor/task_result.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/executor/task_result.py 2016-09-29 16:06:33.000000000 +0000 @@ -62,7 +62,7 @@ return self._check_key('unreachable') def _check_key(self, key): - if 'results' in self._result and self._task.loop: + if self._result.get('results', []) and self._task.loop: flag = False for res in self._result.get('results', []): if isinstance(res, dict): diff -Nru ansible-2.1.1.0/lib/ansible/galaxy/api.py ansible-2.1.2.0/lib/ansible/galaxy/api.py --- ansible-2.1.1.0/lib/ansible/galaxy/api.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/galaxy/api.py 2016-09-29 16:06:33.000000000 +0000 @@ -127,6 +127,7 @@ data = json.load(resp) return data + @g_connect def create_import_task(self, github_user, github_repo, reference=None): """ Post an import request diff -Nru ansible-2.1.1.0/lib/ansible/inventory/ini.py ansible-2.1.2.0/lib/ansible/inventory/ini.py --- ansible-2.1.1.0/lib/ansible/inventory/ini.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/inventory/ini.py 2016-09-29 16:06:33.000000000 +0000 @@ -124,7 +124,7 @@ del pending_declarations[groupname] continue - elif line.startswith('['): + elif line.startswith('[') and line.endswith(']'): self._raise_error("Invalid section entry: '%s'. Please make sure that there are no spaces" % line + \ "in the section entry, and that there are no other invalid characters") diff -Nru ansible-2.1.1.0/lib/ansible/inventory/__init__.py ansible-2.1.2.0/lib/ansible/inventory/__init__.py --- ansible-2.1.1.0/lib/ansible/inventory/__init__.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/inventory/__init__.py 2016-09-29 16:06:33.000000000 +0000 @@ -90,8 +90,6 @@ self.clear_pattern_cache() self.parse_inventory(host_list) - if self.localhost is None: - self.localhost = self._create_implicit_localhost() def serialize(self): data = dict() @@ -128,13 +126,15 @@ display.vvv("Unable to parse address from hostname, leaving unchanged: %s" % to_unicode(e)) host = h port = None + new_host = Host(host, port) + if h in C.LOCALHOST: + # set default localhost from inventory to avoid creating an implicit one. Last localhost defined 'wins'. + if self.localhost is not None: + display.warning("A duplicate localhost-like entry was found (%s). First found localhost was %s" % (h, self.localhost.name)) + display.vvvv("Set default localhost to %s" % h) + self.localhost = new_host all.add_host(new_host) - if new_host.name in C.LOCALHOST: - if self.localhost is None: - self.localhost = new_host - else: - display.warning("A duplicate localhost-like entry was found (%s). First found localhost was %s" % (new_host.name, self.localhost.name)) elif self._loader.path_exists(host_list): #TODO: switch this to a plugin loader and a 'condition' per plugin on which it should be tried, restoring 'inventory pllugins' if self.is_directory(host_list): @@ -470,22 +470,30 @@ for host in matching_hosts: __append_host_to_results(host) + if pattern in C.LOCALHOST and len(results) == 0: + new_host = self._create_implicit_localhost(pattern) + results.append(new_host) return results - def _create_implicit_localhost(self, pattern='localhost'): - new_host = Host(pattern) - new_host.address = "127.0.0.1" - new_host.implicit = True - new_host.vars = self.get_host_vars(new_host) - new_host.set_variable("ansible_connection", "local") - if "ansible_python_interpreter" not in new_host.vars: - py_interp = sys.executable - if not py_interp: - # sys.executable is not set in some cornercases. #13585 - display.warning('Unable to determine python interpreter from sys.executable. Using /usr/bin/python default. You can correct this by setting ansible_python_interpreter for localhost') - py_interp = '/usr/bin/python' - new_host.set_variable("ansible_python_interpreter", py_interp) - self.get_group("ungrouped").add_host(new_host) + def _create_implicit_localhost(self, pattern): + + if self.localhost: + new_host = self.localhost + else: + new_host = Host(pattern) + new_host.address = "127.0.0.1" + new_host.implicit = True + new_host.vars = self.get_host_vars(new_host) + new_host.set_variable("ansible_connection", "local") + if "ansible_python_interpreter" not in new_host.vars: + py_interp = sys.executable + if not py_interp: + # sys.executable is not set in some cornercases. #13585 + display.warning('Unable to determine python interpreter from sys.executable. Using /usr/bin/python default. You can correct this by setting ansible_python_interpreter for localhost') + py_interp = '/usr/bin/python' + new_host.set_variable("ansible_python_interpreter", py_interp) + self.get_group("ungrouped").add_host(new_host) + self.localhost = new_host return new_host def clear_pattern_cache(self): @@ -509,14 +517,28 @@ return self._hosts_cache[hostname] def _get_host(self, hostname): - if hostname in C.LOCALHOST and self.localhost: - self.localhost matching_host = None - for group in self.groups.values(): - for host in group.get_hosts(): - if hostname == host.name: - matching_host = host - self._hosts_cache[host.name] = host + if hostname in C.LOCALHOST: + if self.localhost: + matching_host= self.localhost + else: + for host in self.get_group('all').get_hosts(): + if host.name in C.LOCALHOST: + matching_host = host + break + if not matching_host: + matching_host = self._create_implicit_localhost(hostname) + # update caches + self._hosts_cache[hostname] = matching_host + for host in C.LOCALHOST.difference((hostname,)): + self._hosts_cache[host] = self._hosts_cache[hostname] + else: + for group in self.groups.values(): + for host in group.get_hosts(): + if host not in self._hosts_cache: + self._hosts_cache[host.name] = host + if hostname == host.name: + matching_host = host return matching_host def get_group(self, groupname): @@ -695,6 +717,12 @@ """ # Only update things if dir is a different playbook basedir if dir_name != self._playbook_basedir: + # we're changing the playbook basedir, so if we had set one previously + # clear the host/group vars entries from the VariableManager so they're + # not incorrectly used by playbooks from different directories + if self._playbook_basedir: + self._variable_manager.clear_playbook_hostgroup_vars_files(self._playbook_basedir) + self._playbook_basedir = dir_name # get group vars from group_vars/ files # TODO: excluding the new_pb_basedir directory may result in group_vars @@ -760,10 +788,10 @@ # look in both the inventory base directory and the playbook base directory # unless we do an update for a new playbook base dir - if not new_pb_basedir: + if not new_pb_basedir and _playbook_basedir: basedirs = [_basedir, _playbook_basedir] else: - basedirs = [_playbook_basedir] + basedirs = [_basedir] for basedir in basedirs: # this can happen from particular API usages, particularly if not run diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/cloud/amazon/ec2.py ansible-2.1.2.0/lib/ansible/modules/core/cloud/amazon/ec2.py --- ansible-2.1.1.0/lib/ansible/modules/core/cloud/amazon/ec2.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/cloud/amazon/ec2.py 2016-09-29 16:06:37.000000000 +0000 @@ -1279,14 +1279,27 @@ # Check that our instances are not in the state we want to take # Check (and eventually change) instances attributes and instances state - running_instances_array = [] + existing_instances_array = [] for res in ec2.get_all_instances(instance_ids, filters=filters): for inst in res.instances: # Check "source_dest_check" attribute - if inst.get_attribute('sourceDestCheck')['sourceDestCheck'] != source_dest_check: - inst.modify_attribute('sourceDestCheck', source_dest_check) - changed = True + try: + if inst.vpc_id is not None and inst.get_attribute('sourceDestCheck')['sourceDestCheck'] != source_dest_check: + inst.modify_attribute('sourceDestCheck', source_dest_check) + changed = True + except boto.exception.EC2ResponseError as exc: + # instances with more than one Elastic Network Interface will + # fail, because they have the sourceDestCheck attribute defined + # per-interface + if exc.code == 'InvalidInstanceID': + for interface in inst.interfaces: + if interface.source_dest_check != source_dest_check: + ec2.modify_network_interface_attribute(interface.id, "sourceDestCheck", source_dest_check) + changed = True + else: + module.fail_json(msg='Failed to handle source_dest_check state for instance {0}, error: {1}'.format(inst.id, exc), + exception=traceback.format_exc(exc)) # Check "termination_protection" attribute if inst.get_attribute('disableApiTermination')['disableApiTermination'] != termination_protection: @@ -1304,7 +1317,9 @@ except EC2ResponseError, e: module.fail_json(msg='Unable to change state for instance {0}, error: {1}'.format(inst.id, e)) changed = True + existing_instances_array.append(inst.id) + instance_ids = list(set(existing_instances_array + (instance_ids or []))) ## Wait for all the instances to finish starting or stopping wait_timeout = time.time() + wait_timeout while wait and wait_timeout > time.time(): diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/cloud/amazon/ec2_vpc.py ansible-2.1.2.0/lib/ansible/modules/core/cloud/amazon/ec2_vpc.py --- ansible-2.1.1.0/lib/ansible/modules/core/cloud/amazon/ec2_vpc.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/cloud/amazon/ec2_vpc.py 2016-09-29 16:06:37.000000000 +0000 @@ -408,7 +408,7 @@ for subnet in subnets: add_subnet = True subnet_tags_current = True - new_subnet_tags = subnet.get('resource_tags', None) + new_subnet_tags = subnet.get('resource_tags', {}) subnet_tags_delete = [] for csn in current_subnets: @@ -444,7 +444,7 @@ if add_subnet: try: new_subnet = vpc_conn.create_subnet(vpc.id, subnet['cidr'], subnet.get('az', None)) - new_subnet_tags = subnet.get('resource_tags', None) + new_subnet_tags = subnet.get('resource_tags', {}) if new_subnet_tags: # Sometimes AWS takes its time to create a subnet and so using new subnets's id # to create tags results in exception. diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/cloud/amazon/iam_cert.py ansible-2.1.2.0/lib/ansible/modules/core/cloud/amazon/iam_cert.py --- ansible-2.1.1.0/lib/ansible/modules/core/cloud/amazon/iam_cert.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/cloud/amazon/iam_cert.py 2016-09-29 16:06:37.000000000 +0000 @@ -232,7 +232,7 @@ new_name=dict(default=None, required=False), path=dict(default='/', required=False), new_path=dict(default=None, required=False), - dup_ok=dict(default=False, required=False, choices=[False, True]) + dup_ok=dict(default=False, required=False, type='bool') ) ) diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/cloud/docker/docker_container.py ansible-2.1.2.0/lib/ansible/modules/core/cloud/docker/docker_container.py --- ansible-2.1.1.0/lib/ansible/modules/core/cloud/docker/docker_container.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/cloud/docker/docker_container.py 2016-09-29 16:06:37.000000000 +0000 @@ -1644,8 +1644,8 @@ self.container_stop(container.Id) self.container_remove(container.Id) - def fail(self, msg): - self.client.module.fail_json(msg=msg) + def fail(self, msg, **kwargs): + self.client.module.fail_json(msg=msg, **kwargs) def _get_container(self, container): ''' @@ -1773,6 +1773,13 @@ self.client.start(container=container_id) except Exception as exc: self.fail("Error starting container %s: %s" % (container_id, str(exc))) + + if not self.parameters.detach: + status = self.client.wait(container_id) + if status != 0: + output = self.client.logs(container_id, stdout=True, stderr=True, stream=False, timestamps=False) + self.fail(output, status=status) + return self._get_container(container_id) def container_remove(self, container_id, v=False, link=False, force=False): diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/cloud/docker/docker_image.py ansible-2.1.2.0/lib/ansible/modules/core/cloud/docker/docker_image.py --- ansible-2.1.1.0/lib/ansible/modules/core/cloud/docker/docker_image.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/cloud/docker/docker_image.py 2016-09-29 16:06:37.000000000 +0000 @@ -43,8 +43,8 @@ version_added: "2.0" force: description: - - Use with absent state to un-tag and remove all images matching the specified name. Use with states 'present' - and 'tagged' to take action even when an image already exists. + - Use with state I(absent) to un-tag and remove all images matching the specified name. Use with state + C(present) to build, load or pull an image when the image already exists. default: false version_added: "2.1" http_timeout: @@ -97,12 +97,14 @@ from Docker Hub. To build the image, provide a path value set to a directory containing a context and Dockerfile. To load an image, specify load_path to provide a path to an archive file. To tag an image to a repository, provide a repository path. If the name contains a repository path, it will be pushed. - - "NOTE: 'build' is DEPRECATED. Specifying 'build' will behave the same as 'present'." + - "NOTE: C(build) is DEPRECATED and will be removed in release 2.3. Specifying C(build) will behave the + same as C(present)." + required: false default: present choices: - absent - present - - build (DEPRECATED) + - build tag: description: - Used to select an image when pulling. Will be added to the image when pushing, tagging or building. Defaults to @@ -504,7 +506,7 @@ pull=dict(type='bool', default=True), repository=dict(type='str'), rm=dict(type='bool', default=True), - state=dict(type='str', choices=['absent', 'present'], default='present'), + state=dict(type='str', choices=['absent', 'present', 'build'], default='present'), tag=dict(type='str', default='latest'), use_tls=dict(type='str', default='no', choices=['no', 'encrypt', 'verify']) ) diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/cloud/docker/docker.py ansible-2.1.2.0/lib/ansible/modules/core/cloud/docker/docker.py --- ansible-2.1.1.0/lib/ansible/modules/core/cloud/docker/docker.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/cloud/docker/docker.py 2016-09-29 16:06:37.000000000 +0000 @@ -561,6 +561,8 @@ if isinstance(number, int): return number + if number.isdigit(): + return int(number) if number[-1] == suffixes[0] and number[-2].isdigit(): return number[:-1] @@ -1855,7 +1857,7 @@ volumes_from = dict(default=None, type='list'), links = dict(default=None, type='list'), devices = dict(default=None, type='list'), - memory_limit = dict(default=0, type='int'), + memory_limit = dict(default=0), memory_swap = dict(default=0, type='int'), cpu_shares = dict(default=0, type='int'), docker_url = dict(), @@ -1874,7 +1876,7 @@ domainname = dict(default=None), env = dict(type='dict'), env_file = dict(default=None), - dns = dict(), + dns = dict(default=None, type='list'), detach = dict(default=True, type='bool'), state = dict(default='started', choices=['present', 'started', 'reloaded', 'restarted', 'stopped', 'killed', 'absent', 'running']), signal = dict(default=None), diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/cloud/openstack/os_network.py ansible-2.1.2.0/lib/ansible/modules/core/cloud/openstack/os_network.py --- ansible-2.1.1.0/lib/ansible/modules/core/cloud/openstack/os_network.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/cloud/openstack/os_network.py 2016-09-29 16:06:37.000000000 +0000 @@ -222,8 +222,12 @@ if provider and StrictVersion(shade.__version__) < StrictVersion('1.5.0'): module.fail_json(msg="Shade >= 1.5.0 required to use provider options") - net = cloud.create_network(name, shared, admin_state_up, - external, provider, project_id) + if project_id is not None: + net = cloud.create_network(name, shared, admin_state_up, + external, provider, project_id) + else: + net = cloud.create_network(name, shared, admin_state_up, + external, provider) changed = True else: changed = False diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/database/mysql/mysql_db.py ansible-2.1.2.0/lib/ansible/modules/core/database/mysql/mysql_db.py --- ansible-2.1.1.0/lib/ansible/modules/core/database/mysql/mysql_db.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/database/mysql/mysql_db.py 2016-09-29 16:06:37.000000000 +0000 @@ -313,7 +313,7 @@ else: rc, stdout, stderr = db_dump(module, login_host, login_user, login_password, db, target, all_databases, - login_port, config_file, socket, ssl_cert, ssl_key, ssl_ca) + login_port, config_file, socket, ssl_cert, ssl_key, ssl_ca, single_transaction, quick) if rc != 0: module.fail_json(msg="%s" % stderr) else: diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/database/postgresql/postgresql_db.py ansible-2.1.2.0/lib/ansible/modules/core/database/postgresql/postgresql_db.py --- ansible-2.1.1.0/lib/ansible/modules/core/database/postgresql/postgresql_db.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/database/postgresql/postgresql_db.py 2016-09-29 16:06:37.000000000 +0000 @@ -286,11 +286,11 @@ try: if module.check_mode: if state == "absent": - changed = not db_exists(cursor, db) + changed = db_exists(cursor, db) elif state == "present": changed = not db_matches(cursor, db, owner, template, encoding, lc_collate, lc_ctype) - module.exit_json(changed=changed,db=db) + module.exit_json(changed=changed, db=db) if state == "absent": try: diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/files/ini_file.py ansible-2.1.2.0/lib/ansible/modules/core/files/ini_file.py --- ansible-2.1.1.0/lib/ansible/modules/core/files/ini_file.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/files/ini_file.py 2016-09-29 16:06:37.000000000 +0000 @@ -104,22 +104,23 @@ import ConfigParser import sys import os +import re # ============================================================== # match_opt def match_opt(option, line): option = re.escape(option) - return re.match('%s *=' % option, line) \ - or re.match('# *%s *=' % option, line) \ - or re.match('; *%s *=' % option, line) + return re.match(' *%s( |\t)*=' % option, line) \ + or re.match('# *%s( |\t)*=' % option, line) \ + or re.match('; *%s( |\t)*=' % option, line) # ============================================================== # match_active_opt def match_active_opt(option, line): option = re.escape(option) - return re.match('%s *=' % option, line) + return re.match(' *%s( |\t)*=' % option, line) # ============================================================== # do_ini @@ -156,8 +157,12 @@ if within_section: if state == 'present': # insert missing option line at the end of the section - ini_lines.insert(index, assignment_format % (option, value)) - changed = True + for i in range(index, 0, -1): + # search backwards for previous non-blank or non-comment line + if not re.match(r'^[ \t]*([#;].*)?$', ini_lines[i - 1]): + ini_lines.insert(i, assignment_format % (option, value)) + changed = True + break elif state == 'absent' and not option: # remove the entire section del ini_lines[section_start:index] diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/files/synchronize.py ansible-2.1.2.0/lib/ansible/modules/core/files/synchronize.py --- ansible-2.1.1.0/lib/ansible/modules/core/files/synchronize.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/files/synchronize.py 2016-09-29 16:06:37.000000000 +0000 @@ -35,7 +35,7 @@ dest_port: description: - Port number for ssh on the destination host. Prior to ansible 2.0, the ansible_ssh_port inventory var took precedence over this value. - default: Value of ansible_ssh_port for this host, remote_port config setting, or 22 if none of those are set + default: Value of ansible_ssh_port for this host, remote_port config setting, or the value from ssh client configuration if none of those are set version_added: "1.5" mode: description: @@ -281,7 +281,7 @@ argument_spec = dict( src = dict(required=True), dest = dict(required=True), - dest_port = dict(default=22, type='int'), + dest_port = dict(default=None, type='int'), delete = dict(default='no', type='bool'), private_key = dict(default=None), rsync_path = dict(default=None), @@ -407,14 +407,17 @@ if ssh_args: ssh_opts = '%s %s' % (ssh_opts, ssh_args) - if source.startswith('rsync://') and dest.startswith('rsync://'): + if source.startswith('"rsync://') and dest.startswith('"rsync://'): module.fail_json(msg='either src or dest must be a localhost', rc=1) - if not source.startswith('rsync://') and not dest.startswith('rsync://'): - if dest_port != 22: + if not source.startswith('"rsync://') and not dest.startswith('"rsync://'): + # If the user specified a port value + # Note: The action plugin takes care of setting this to a port from + # inventory if the user didn't specify an explict dest_port + if dest_port is not None: cmd += " --rsh 'ssh %s %s -o Port=%s'" % (private_key, ssh_opts, dest_port) else: - cmd += " --rsh 'ssh %s %s'" % (private_key, ssh_opts) # need ssh param + cmd += " --rsh 'ssh %s %s'" % (private_key, ssh_opts) if rsync_path: cmd = cmd + " --rsync-path=%s" % (rsync_path) diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/files/unarchive.py ansible-2.1.2.0/lib/ansible/modules/core/files/unarchive.py --- ansible-2.1.1.0/lib/ansible/modules/core/files/unarchive.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/files/unarchive.py 2016-09-29 16:06:37.000000000 +0000 @@ -123,7 +123,7 @@ MODE_DIFF_RE = re.compile(r': Mode differs$') #NEWER_DIFF_RE = re.compile(r' is newer or same age.$') MISSING_FILE_RE = re.compile(r': Warning: Cannot stat: No such file or directory$') -ZIP_FILE_MODE_RE = re.compile(r'([r-][w-][stx-]){3}') +ZIP_FILE_MODE_RE = re.compile(r'([r-][w-][SsTtx-]){3}') # When downloading an archive, how much of the archive to download before # saving to a tempfile (64k) BUFSIZE = 65536 @@ -312,7 +312,7 @@ version = pcs[1] ostype = pcs[2] size = int(pcs[3]) - path = pcs[7] + path = unicode(pcs[7], 'utf-8') # Skip excluded files if path in self.excludes: @@ -396,6 +396,7 @@ elif stat.S_ISREG(st.st_mode) and timestamp < st.st_mtime: # Add to excluded files, ignore other changes out += 'File %s is newer, excluding file\n' % path + self.excludes.append(path) continue else: if timestamp != st.st_mtime: @@ -501,11 +502,11 @@ cmd = '%s -o "%s"' % (self.cmd_path, self.src) if self.opts: cmd += ' ' + ' '.join(self.opts) - if self.includes: - cmd += ' "' + '" "'.join(self.includes) + '"' - # We don't need to handle excluded files, since we simply do not include them -# if self.excludes: -# cmd += ' -x ' + ' '.join(self.excludes) + # NOTE: Including (changed) files as arguments is problematic (limits on command line/arguments) +# if self.includes: +# cmd += ' "' + '" "'.join(self.includes) + '"' + if self.excludes: + cmd += ' -x ' + ' '.join(self.excludes) cmd += ' -d "%s"' % self.dest rc, out, err = self.module.run_command(cmd) return dict(cmd=cmd, rc=rc, out=out, err=err) @@ -550,7 +551,7 @@ if self.excludes: cmd += ' --exclude="' + '" --exclude="'.join(self.excludes) + '"' cmd += ' -f "%s"' % self.src - rc, out, err = self.module.run_command(cmd) + rc, out, err = self.module.run_command(cmd, environ_update=dict(LANG='C', LC_ALL='C', LC_MESSAGES='C')) if rc != 0: raise UnarchiveError('Unable to list files in the archive') @@ -577,7 +578,7 @@ if self.excludes: cmd += ' --exclude="' + '" --exclude="'.join(self.excludes) + '"' cmd += ' -f "%s"' % self.src - rc, out, err = self.module.run_command(cmd) + rc, out, err = self.module.run_command(cmd, environ_update=dict(LANG='C', LC_ALL='C', LC_MESSAGES='C')) # Check whether the differences are in something that we're # setting anyway @@ -618,7 +619,7 @@ if self.excludes: cmd += ' --exclude="' + '" --exclude="'.join(self.excludes) + '"' cmd += ' -f "%s"' % (self.src) - rc, out, err = self.module.run_command(cmd, cwd=self.dest) + rc, out, err = self.module.run_command(cmd, cwd=self.dest, environ_update=dict(LANG='C', LC_ALL='C', LC_MESSAGES='C')) return dict(cmd=cmd, rc=rc, out=out, err=err) def can_handle_archive(self): @@ -690,9 +691,6 @@ # supports_check_mode = True, ) - # We screenscrape a huge amount of commands so use C locale anytime we do - module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C') - src = os.path.expanduser(module.params['src']) dest = os.path.expanduser(module.params['dest']) copy = module.params['copy'] diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/network/basics/get_url.py ansible-2.1.2.0/lib/ansible/modules/core/network/basics/get_url.py --- ansible-2.1.1.0/lib/ansible/modules/core/network/basics/get_url.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/network/basics/get_url.py 2016-09-29 16:06:37.000000000 +0000 @@ -207,7 +207,7 @@ module.exit_json(url=url, dest=dest, changed=False, msg=info.get('msg', '')) # create a temporary file and copy content to do checksum-based replacement - if info['status'] != 200 and not url.startswith('file:/'): + if info['status'] != 200 and not url.startswith('file:/') and not (url.startswith('ftp:/') and info.get('msg', '').startswith('OK')): module.fail_json(msg="Request failed", status_code=info['status'], response=info['msg'], url=url, dest=dest) if tmp_dest != '': @@ -288,7 +288,7 @@ # Parse headers to dict if module.params['headers']: try: - headers = dict(item.split(':') for item in module.params['headers'].split(',')) + headers = dict(item.split(':', 1) for item in module.params['headers'].split(',')) except: module.fail_json(msg="The header parameter requires a key:value,key:value syntax to be properly parsed.") else: @@ -343,6 +343,11 @@ mtime = os.path.getmtime(dest) last_mod_time = datetime.datetime.utcfromtimestamp(mtime) + # If the checksum does not match we have to force the download + # because last_mod_time may be newer than on remote + if checksum_mismatch: + force = True + # download to tmpsrc tmpsrc, info = url_get(module, url, dest, use_proxy, last_mod_time, force, timeout, headers, tmp_dest) diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/network/nxos/nxos_feature.py ansible-2.1.2.0/lib/ansible/modules/core/network/nxos/nxos_feature.py --- ansible-2.1.1.0/lib/ansible/modules/core/network/nxos/nxos_feature.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/network/nxos/nxos_feature.py 2016-09-29 16:06:37.000000000 +0000 @@ -24,15 +24,17 @@ description: - Offers ability to enable and disable features in NX-OS extends_documentation_fragment: nxos -author: Jason Edelman (@jedelman8), Gabriele Gerbino (@GGabriele) +author: + - Jason Edelman (@jedelman8) + - Gabriele Gerbino (@GGabriele) options: feature: description: - - Name of feature + - Name of feature. required: true state: description: - - Desired state of the feature + - Desired state of the feature. required: false default: 'enabled' choices: ['enabled','disabled'] @@ -89,36 +91,20 @@ def execute_config_command(commands, module): try: module.configure(commands) - except ShellError, clie: + except ShellError: + clie = get_exception() module.fail_json(msg='Error sending CLI commands', error=str(clie), commands=commands) -def get_cli_body_ssh(command, response, module): - """Get response for when transport=cli. This is kind of a hack and mainly - needed because these modules were originally written for NX-API. And - not every command supports "| json" when using cli/ssh. As such, we assume - if | json returns an XML string, it is a valid command, but that the - resource doesn't exist yet. - """ - if 'xml' in response[0]: - body = [] - else: - try: - body = [json.loads(response[0])] - except ValueError: - module.fail_json(msg='Command does not support JSON output', - command=command) - return body - - def execute_show(cmds, module, command_type=None): try: if command_type: response = module.execute(cmds, command_type=command_type) else: response = module.execute(cmds) - except ShellError, clie: + except ShellError: + clie = get_exception() module.fail_json(msg='Error sending {0}'.format(cmds), error=str(clie)) return response @@ -126,10 +112,8 @@ def execute_show_command(command, module, command_type='cli_show'): if module.params['transport'] == 'cli': - command += ' | json' cmds = [command] - response = execute_show(cmds, module) - body = get_cli_body_ssh(command, response, module) + body = execute_show(cmds, module) elif module.params['transport'] == 'nxapi': cmds = [command] body = execute_show(cmds, module, command_type=command_type) @@ -152,27 +136,32 @@ def get_available_features(feature, module): available_features = {} + feature_regex = '(?P\S+)\s+\d+\s+(?P.*)' command = 'show feature' - body = execute_show_command(command, module) - try: - body = body[0]['TABLE_cfcFeatureCtrlTable']['ROW_cfcFeatureCtrlTable'] - except (TypeError, IndexError): - return available_features - - for each_feature in body: - feature = each_feature['cfcFeatureCtrlName2'] - state = each_feature['cfcFeatureCtrlOpStatus2'] + body = execute_show_command(command, module, command_type='cli_show_ascii') + split_body = body[0].splitlines() - if 'enabled' in state: - state = 'enabled' + for line in split_body: + try: + match_feature = re.match(feature_regex, line, re.DOTALL) + feature_group = match_feature.groupdict() + feature = feature_group['feature'] + state = feature_group['state'] + except AttributeError: + feature = '' + state = '' + + if feature and state: + if 'enabled' in state: + state = 'enabled' - if feature not in available_features.keys(): - available_features[feature] = state - else: - if (available_features[feature] == 'disabled' and - state == 'enabled'): + if feature not in available_features.keys(): available_features[feature] = state + else: + if (available_features[feature] == 'disabled' and + state == 'enabled'): + available_features[feature] = state return available_features diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/packaging/os/yum.py ansible-2.1.2.0/lib/ansible/modules/core/packaging/os/yum.py --- ansible-2.1.1.0/lib/ansible/modules/core/packaging/os/yum.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/packaging/os/yum.py 2016-09-29 16:06:37.000000000 +0000 @@ -477,24 +477,11 @@ finally: os.close(fd) - return '%s-%s-%s.%s' % (header[rpm.RPMTAG_NAME], + return '%s-%s-%s.%s' % (header[rpm.RPMTAG_NAME], header[rpm.RPMTAG_VERSION], header[rpm.RPMTAG_RELEASE], header[rpm.RPMTAG_ARCH]) - -def local_name(module, path): - """return package name of a local rpm passed in""" - - ts = rpm.TransactionSet() - ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES) - fd = os.open(path, os.O_RDONLY) - try: - header = ts.hdrFromFdno(fd) - finally: - os.close(fd) - return header[rpm.RPMTAG_NAME] - def pkg_to_dict(pkgstr): if pkgstr.strip(): @@ -569,9 +556,10 @@ res['msg'] += "No Package file matching '%s' found on system" % spec module.fail_json(**res) - pkg_name = local_name(module, spec) + nvra = local_nvra(module, spec) + # look for them in the rpmdb - if is_installed(module, repoq, pkg_name, conf_file, en_repos=en_repos, dis_repos=dis_repos): + if is_installed(module, repoq, nvra, conf_file, en_repos=en_repos, dis_repos=dis_repos): # if they are there, skip it continue pkg = spec @@ -592,8 +580,8 @@ shutil.rmtree(tempdir) module.fail_json(msg="Failure downloading %s, %s" % (spec, e)) - pkg_name = local_name(module, package) - if is_installed(module, repoq, pkg_name, conf_file, en_repos=en_repos, dis_repos=dis_repos): + nvra = local_nvra(module, package) + if is_installed(module, repoq, nvra, conf_file, en_repos=en_repos, dis_repos=dis_repos): # if it's there, skip it continue pkg = package diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/shippable.yml ansible-2.1.2.0/lib/ansible/modules/core/shippable.yml --- ansible-2.1.1.0/lib/ansible/modules/core/shippable.yml 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/shippable.yml 2016-09-29 16:06:37.000000000 +0000 @@ -0,0 +1,41 @@ +language: python + +env: + matrix: + - TEST=none + +matrix: + exclude: + - env: TEST=none + include: + - env: TEST=sanity INSTALL_DEPS=1 +build: + pre_ci_boot: + options: "--privileged=false --net=bridge" + ci: + - test/utils/shippable/ci.sh + +integrations: + notifications: + - integrationName: email + type: email + on_success: never + on_failure: never + on_start: never + on_pull_request: never + - integrationName: irc + type: irc + recipients: + - "chat.freenode.net#ansible-notices" + on_success: change + on_failure: always + on_start: never + on_pull_request: always + - integrationName: slack + type: slack + recipients: + - "#shippable" + on_success: change + on_failure: always + on_start: never + on_pull_request: never diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/source_control/subversion.py ansible-2.1.2.0/lib/ansible/modules/core/source_control/subversion.py --- ansible-2.1.1.0/lib/ansible/modules/core/source_control/subversion.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/source_control/subversion.py 2016-09-29 16:06:37.000000000 +0000 @@ -219,7 +219,7 @@ # We screenscrape a huge amount of svn commands so use C locale anytime we # call run_command() - module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C') + module.run_command_environ_update = dict(LANG='C', LC_MESSAGES='C') svn = Subversion(module, dest, repo, revision, username, password, svn_path) diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/system/cron.py ansible-2.1.2.0/lib/ansible/modules/core/system/cron.py --- ansible-2.1.1.0/lib/ansible/modules/core/system/cron.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/system/cron.py 2016-09-29 16:06:37.000000000 +0000 @@ -381,6 +381,9 @@ return [] def get_cron_job(self,minute,hour,day,month,weekday,job,special,disabled): + # normalize any leading/trailing newlines (ansible/ansible-modules-core#3791) + job = job.strip('\r\n') + if disabled: disable_prefix = '#' else: @@ -474,7 +477,7 @@ return "%s -l %s" % (pipes.quote(CRONCMD), pipes.quote(self.user)) elif platform.system() == 'HP-UX': return "%s %s %s" % (CRONCMD , '-l', pipes.quote(self.user)) - else: + elif os.getlogin() != self.user: user = '-u %s' % pipes.quote(self.user) return "%s %s %s" % (CRONCMD , user, '-l') @@ -486,7 +489,7 @@ if self.user: if platform.system() in ['SunOS', 'HP-UX', 'AIX']: return "chown %s %s ; su '%s' -c '%s %s'" % (pipes.quote(self.user), pipes.quote(path), pipes.quote(self.user), CRONCMD, pipes.quote(path)) - else: + elif os.getlogin() != self.user: user = '-u %s' % pipes.quote(self.user) return "%s %s %s" % (CRONCMD , user, pipes.quote(path)) diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/system/service.py ansible-2.1.2.0/lib/ansible/modules/core/system/service.py --- ansible-2.1.1.0/lib/ansible/modules/core/system/service.py 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/system/service.py 2016-09-29 16:06:37.000000000 +0000 @@ -496,6 +496,8 @@ (rc, out, err) = self.execute_command("%s is-enabled %s" % (self.enable_cmd, service_name,)) if rc == 0: return True + elif out.startswith('disabled'): + return False elif sysv_exists(service_name): return sysv_is_enabled(service_name) else: diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/test/utils/shippable/ci.sh ansible-2.1.2.0/lib/ansible/modules/core/test/utils/shippable/ci.sh --- ansible-2.1.1.0/lib/ansible/modules/core/test/utils/shippable/ci.sh 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/test/utils/shippable/ci.sh 2016-09-29 16:06:37.000000000 +0000 @@ -0,0 +1,7 @@ +#!/bin/bash -eux + +set -o pipefail + +source_root=$(python -c "from os import path; print(path.abspath(path.join(path.dirname('$0'), '../../..')))") + +"${source_root}/test/utils/shippable/${TEST}.sh" 2>&1 | gawk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0; fflush(); }' diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/test/utils/shippable/sanity.sh ansible-2.1.2.0/lib/ansible/modules/core/test/utils/shippable/sanity.sh --- ansible-2.1.1.0/lib/ansible/modules/core/test/utils/shippable/sanity.sh 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/test/utils/shippable/sanity.sh 2016-09-29 16:06:37.000000000 +0000 @@ -0,0 +1,21 @@ +#!/bin/bash -eux + +source_root=$(python -c "from os import path; print(path.abspath(path.join(path.dirname('$0'), '../../..')))") + +install_deps="${INSTALL_DEPS:-}" + +cd "${source_root}" + +if [ "${install_deps}" != "" ]; then + add-apt-repository ppa:fkrull/deadsnakes && apt-get update -qq && apt-get install python2.4 -qq + + pip install git+https://github.com/ansible/ansible.git@stable-2.1#egg=ansible + pip install git+https://github.com/sivel/ansible-testing.git#egg=ansible_testing +fi + +python2.4 -m compileall -fq -i "test/utils/shippable/sanity-test-python24.txt" +python2.4 -m compileall -fq -x "($(printf %s "$(< "test/utils/shippable/sanity-skip-python24.txt"))" | tr '\n' '|')" . +python2.6 -m compileall -fq . +python2.7 -m compileall -fq . + +ansible-validate-modules --exclude '/utilities/|/shippable(/|$)' . diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/test/utils/shippable/sanity-skip-python24.txt ansible-2.1.2.0/lib/ansible/modules/core/test/utils/shippable/sanity-skip-python24.txt --- ansible-2.1.1.0/lib/ansible/modules/core/test/utils/shippable/sanity-skip-python24.txt 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/test/utils/shippable/sanity-skip-python24.txt 2016-09-29 16:06:37.000000000 +0000 @@ -0,0 +1 @@ +/cloud/ diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/test/utils/shippable/sanity-test-python24.txt ansible-2.1.2.0/lib/ansible/modules/core/test/utils/shippable/sanity-test-python24.txt --- ansible-2.1.1.0/lib/ansible/modules/core/test/utils/shippable/sanity-test-python24.txt 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/test/utils/shippable/sanity-test-python24.txt 2016-09-29 16:06:37.000000000 +0000 @@ -0,0 +1,2 @@ +cloud/amazon/_ec2_ami_search.py +cloud/amazon/ec2_facts.py diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/test-docs.sh ansible-2.1.2.0/lib/ansible/modules/core/test-docs.sh --- ansible-2.1.1.0/lib/ansible/modules/core/test-docs.sh 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/test-docs.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -#!/bin/sh -set -x - -CHECKOUT_DIR=".ansible-checkout" -MOD_REPO="$1" - -# Hidden file to avoid the module_formatter recursing into the checkout -git clone https://github.com/ansible/ansible "$CHECKOUT_DIR" -cd "$CHECKOUT_DIR" -git submodule update --init -rm -rf "lib/ansible/modules/$MOD_REPO" -ln -s "$TRAVIS_BUILD_DIR/" "lib/ansible/modules/$MOD_REPO" - -pip install -U Jinja2 PyYAML setuptools six pycrypto sphinx - -. ./hacking/env-setup -PAGER=/bin/cat bin/ansible-doc -l -if [ $? -ne 0 ] ; then - exit $? -fi -make -C docsite diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/.travis.yml ansible-2.1.2.0/lib/ansible/modules/core/.travis.yml --- ansible-2.1.1.0/lib/ansible/modules/core/.travis.yml 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/.travis.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -sudo: false -language: python -python: - - "2.7" -addons: - apt: - sources: - - deadsnakes - packages: - - python2.4 - - python2.6 -install: - - pip install git+https://github.com/ansible/ansible.git@stable-2.1#egg=ansible - - pip install git+https://github.com/sivel/ansible-testing.git#egg=ansible_testing -script: - - python2.4 -m compileall -fq -x 'cloud/' . - - python2.4 -m compileall -fq cloud/amazon/_ec2_ami_search.py cloud/amazon/ec2_facts.py - - python2.6 -m compileall -fq . - - python2.7 -m compileall -fq . - - ansible-validate-modules --exclude 'utilities/' . - #- ansible-validate-modules --exclude 'cloud/amazon/ec2_lc\.py|cloud/amazon/ec2_scaling_policy\.py|cloud/amazon/ec2_scaling_policy\.py|cloud/amazon/ec2_asg\.py|cloud/azure/azure\.py|packaging/os/rhn_register\.py|network/openswitch/ops_template\.py|system/hostname\.py|utilities/' . - #- ./test-docs.sh core diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/VERSION ansible-2.1.2.0/lib/ansible/modules/core/VERSION --- ansible-2.1.1.0/lib/ansible/modules/core/VERSION 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/VERSION 2016-09-29 16:06:37.000000000 +0000 @@ -1 +1 @@ -2.1.1.0 1 +2.1.2.0 1 diff -Nru ansible-2.1.1.0/lib/ansible/modules/core/windows/win_user.ps1 ansible-2.1.2.0/lib/ansible/modules/core/windows/win_user.ps1 --- ansible-2.1.1.0/lib/ansible/modules/core/windows/win_user.ps1 2016-07-28 17:54:41.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/core/windows/win_user.ps1 2016-09-29 16:06:37.000000000 +0000 @@ -137,8 +137,16 @@ [void][system.reflection.assembly]::LoadWithPartialName('System.DirectoryServices.AccountManagement') $host_name = [System.Net.Dns]::GetHostName() $pc = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext 'Machine', $host_name - # ValidateCredentials fails if PasswordExpired == 1 - If (!$pc.ValidateCredentials($username, $password)) { + + # ValidateCredentials will fail if either of these are true- just force update... + If($user_obj.AccountDisabled -or $user_obj.PasswordExpired) { + $password_match = $false + } + Else { + $password_match = $pc.ValidateCredentials($username, $password) + } + + If (-not $password_match) { $user_obj.SetPassword($password) $result.changed = $true } diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_route_table.py ansible-2.1.2.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_route_table.py --- ansible-2.1.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_route_table.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/cloud/amazon/ec2_vpc_route_table.py 2016-09-29 16:06:39.000000000 +0000 @@ -183,11 +183,11 @@ filters={'vpc_id': vpc_id, 'tag:Name': subnet_names}) for name in subnet_names: - matching = [s.tags.get('Name') == name for s in subnets_by_name] - if len(matching) == 0: + matching_count = len([1 for s in subnets_by_name if s.tags.get('Name') == name]) + if matching_count == 0: raise AnsibleSubnetSearchException( 'Subnet named "{0}" does not exist'.format(name)) - elif len(matching) > 1: + elif matching_count > 1: raise AnsibleSubnetSearchException( 'Multiple subnets named "{0}"'.format(name)) diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_win_password.py ansible-2.1.2.0/lib/ansible/modules/extras/cloud/amazon/ec2_win_password.py --- ansible-2.1.1.0/lib/ansible/modules/extras/cloud/amazon/ec2_win_password.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/cloud/amazon/ec2_win_password.py 2016-09-29 16:06:39.000000000 +0000 @@ -142,9 +142,15 @@ try: f = open(key_file, 'r') - key = RSA.importKey(f.read(), key_passphrase) - finally: - f.close() + except IOError as e: + module.fail_json(msg = "I/O error (%d) opening key file: %s" % (e.errno, e.strerror)) + else: + try: + with f: + key = RSA.importKey(f.read(), key_passphrase) + except (ValueError, IndexError, TypeError) as e: + module.fail_json(msg = "unable to parse key file") + cipher = PKCS1_v1_5.new(key) sentinel = 'password decryption failed!!!' diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/cloud/amazon/ecs_service_facts.py ansible-2.1.2.0/lib/ansible/modules/extras/cloud/amazon/ecs_service_facts.py --- ansible-2.1.1.0/lib/ansible/modules/extras/cloud/amazon/ecs_service_facts.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/cloud/amazon/ecs_service_facts.py 2016-09-29 16:06:39.000000000 +0000 @@ -55,7 +55,7 @@ - ecs_service_facts: cluster: test-cluster service: console-test-service - details: "true" + details: true # Basic listing example - ecs_service_facts: @@ -201,7 +201,7 @@ argument_spec = ec2_argument_spec() argument_spec.update(dict( - details=dict(required=False, choices=['true', 'false'] ), + details=dict(required=False, type='bool', default=False ), cluster=dict(required=False, type='str' ), service=dict(required=False, type='str' ) )) @@ -214,9 +214,7 @@ if not HAS_BOTO3: module.fail_json(msg='boto3 is required.') - show_details = False - if 'details' in module.params and module.params['details'] == 'true': - show_details = True + show_details = module.params.get('details', False) task_mgr = EcsServiceManager(module) if show_details: diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/cloud/lxc/lxc_container.py ansible-2.1.2.0/lib/ansible/modules/extras/cloud/lxc/lxc_container.py --- ansible-2.1.1.0/lib/ansible/modules/extras/cloud/lxc/lxc_container.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/cloud/lxc/lxc_container.py 2016-09-29 16:06:39.000000000 +0000 @@ -681,45 +681,23 @@ else: return return_dict - def _run_command(self, build_command, unsafe_shell=False, timeout=600): + def _run_command(self, build_command, unsafe_shell=False): """Return information from running an Ansible Command. This will squash the build command list into a string and then execute the command via Ansible. The output is returned to the method. This output is returned as `return_code`, `stdout`, `stderr`. - Prior to running the command the method will look to see if the LXC - lockfile is present. If the lockfile "/var/lock/subsys/lxc" the method - will wait upto 10 minutes for it to be gone; polling every 5 seconds. - :param build_command: Used for the command and all options. :type build_command: ``list`` :param unsafe_shell: Enable or Disable unsafe sell commands. :type unsafe_shell: ``bol`` - :param timeout: Time before the container create process quites. - :type timeout: ``int`` """ - lockfile = '/var/lock/subsys/lxc' - - for _ in xrange(timeout): - if os.path.exists(lockfile): - time.sleep(1) - else: - return self.module.run_command( - ' '.join(build_command), - use_unsafe_shell=unsafe_shell - ) - else: - message = ( - 'The LXC subsystem is locked and after 5 minutes it never' - ' became unlocked. Lockfile [ %s ]' % lockfile - ) - self.failure( - error='LXC subsystem locked', - rc=0, - msg=message - ) + return self.module.run_command( + ' '.join(build_command), + use_unsafe_shell=unsafe_shell + ) def _config(self): """Configure an LXC container. diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/cloud/misc/ovirt.py ansible-2.1.2.0/lib/ansible/modules/extras/cloud/misc/ovirt.py --- ansible-2.1.1.0/lib/ansible/modules/extras/cloud/misc/ovirt.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/cloud/misc/ovirt.py 2016-09-29 16:06:39.000000000 +0000 @@ -333,6 +333,7 @@ vm = conn.vms.get(name=vmname) use_cloud_init = False nics = None + nic = None if hostname or ip or netmask or gateway or domain or dns or rootpw or key: use_cloud_init = True if ip and netmask and gateway: diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/cloud/misc/virt_net.py ansible-2.1.2.0/lib/ansible/modules/extras/cloud/misc/virt_net.py --- ansible-2.1.1.0/lib/ansible/modules/extras/cloud/misc/virt_net.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/cloud/misc/virt_net.py 2016-09-29 16:06:39.000000000 +0000 @@ -483,16 +483,16 @@ else: module.fail_json(msg="Command %s not recognized" % basecmd) - if autostart: + if autostart is not None: if not name: module.fail_json(msg = "state change requires a specified name") res['changed'] = False - if autostart == 'yes': + if autostart: if not v.get_autostart(name): res['changed'] = True res['msg'] = v.set_autostart(name, True) - elif autostart == 'no': + else: if v.get_autostart(name): res['changed'] = True res['msg'] = v.set_autostart(name, False) @@ -511,7 +511,7 @@ command = dict(choices=ALL_COMMANDS), uri = dict(default='qemu:///system'), xml = dict(), - autostart = dict(choices=['yes', 'no']) + autostart = dict(type='bool') ), supports_check_mode = True ) diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/cloud/misc/virt_pool.py ansible-2.1.2.0/lib/ansible/modules/extras/cloud/misc/virt_pool.py --- ansible-2.1.1.0/lib/ansible/modules/extras/cloud/misc/virt_pool.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/cloud/misc/virt_pool.py 2016-09-29 16:06:39.000000000 +0000 @@ -629,16 +629,16 @@ else: module.fail_json(msg="Command %s not recognized" % basecmd) - if autostart: + if autostart is not None: if not name: module.fail_json(msg = "state change requires a specified name") res['changed'] = False - if autostart == 'yes': + if autostart: if not v.get_autostart(name): res['changed'] = True res['msg'] = v.set_autostart(name, True) - elif autostart == 'no': + else: if v.get_autostart(name): res['changed'] = True res['msg'] = v.set_autostart(name, False) @@ -657,7 +657,7 @@ command = dict(choices=ALL_COMMANDS), uri = dict(default='qemu:///system'), xml = dict(), - autostart = dict(choices=['yes', 'no']), + autostart = dict(type='bool'), mode = dict(choices=ALL_MODES), ), supports_check_mode = True diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/cloud/xenserver_facts.py ansible-2.1.2.0/lib/ansible/modules/extras/cloud/xenserver_facts.py --- ansible-2.1.1.0/lib/ansible/modules/extras/cloud/xenserver_facts.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/cloud/xenserver_facts.py 2016-09-29 16:06:39.000000000 +0000 @@ -28,7 +28,13 @@ ''' import platform -import XenAPI + +HAVE_XENAPI = False +try: + import XenAPI + HAVE_XENAPI = True +except ImportError: + pass EXAMPLES = ''' - name: Gather facts from xenserver @@ -158,6 +164,9 @@ def main(): module = AnsibleModule({}) + if not HAVE_XENAPI: + module.fail_json(changed=False, msg="python xen api required for this module") + obj = XenServerFacts() try: session = get_xenapi_session() diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/clustering/consul.py ansible-2.1.2.0/lib/ansible/modules/extras/clustering/consul.py --- ansible-2.1.1.0/lib/ansible/modules/extras/clustering/consul.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/clustering/consul.py 2016-09-29 16:06:39.000000000 +0000 @@ -315,7 +315,7 @@ service_id=result.id, service_name=result.name, service_port=result.port, - checks=map(lambda x: x.to_dict(), service.checks), + checks=[check.to_dict() for check in service.checks], tags=result.tags) @@ -484,8 +484,7 @@ if duration: duration_units = ['ns', 'us', 'ms', 's', 'm', 'h'] if not any((duration.endswith(suffix) for suffix in duration_units)): - raise Exception('Invalid %s %s you must specify units (%s)' % - (name, duration, ', '.join(duration_units))) + duration = "{}s".format(duration) return duration def register(self, consul_api): diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/database/misc/mongodb_user.py ansible-2.1.2.0/lib/ansible/modules/extras/database/misc/mongodb_user.py --- ansible-2.1.1.0/lib/ansible/modules/extras/database/misc/mongodb_user.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/database/misc/mongodb_user.py 2016-09-29 16:06:39.000000000 +0000 @@ -166,11 +166,27 @@ # def user_find(client, user, db_name): + """Check if the user exists. + + Args: + client (cursor): Mongodb cursor on admin database. + user (str): User to check. + db_name (str): User's database. + + Returns: + dict: when user exists, False otherwise. + """ for mongo_user in client["admin"].system.users.find(): - if mongo_user['user'] == user and mongo_user['db'] == db_name: - return mongo_user + if mongo_user['user'] == user: + # NOTE: there is no 'db' field in mongo 2.4. + if 'db' not in mongo_user: + return mongo_user + + if mongo_user["db"] == db_name: + return mongo_user return False + def user_add(module, client, db_name, user, password, roles): #pymongo's user_add is a _create_or_update_user so we won't know if it was changed or updated #without reproducing a lot of the logic in database.py of pymongo diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/database/mysql/mysql_replication.py ansible-2.1.2.0/lib/ansible/modules/extras/database/mysql/mysql_replication.py --- ansible-2.1.1.0/lib/ansible/modules/extras/database/mysql/mysql_replication.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/database/mysql/mysql_replication.py 2016-09-29 16:06:39.000000000 +0000 @@ -184,7 +184,7 @@ module = AnsibleModule( argument_spec = dict( login_user=dict(default=None), - login_password=dict(default=None), + login_password=dict(default=None, no_log=True), login_host=dict(default="localhost"), login_port=dict(default=3306, type='int'), login_unix_socket=dict(default=None), @@ -192,7 +192,7 @@ master_auto_position=dict(default=False, type='bool'), master_host=dict(default=None), master_user=dict(default=None), - master_password=dict(default=None), + master_password=dict(default=None, no_log=True), master_port=dict(default=None, type='int'), master_connect_retry=dict(default=None, type='int'), master_log_file=dict(default=None), @@ -206,7 +206,7 @@ master_ssl_key=dict(default=None), master_ssl_cipher=dict(default=None), connect_timeout=dict(default=30, type='int'), - config_file=dict(default="~/.my.cnf"), + config_file=dict(default="~/.my.cnf", type='path'), ssl_cert=dict(default=None), ssl_key=dict(default=None), ssl_ca=dict(default=None), @@ -238,7 +238,6 @@ ssl_ca = module.params["ssl_ca"] connect_timeout = module.params['connect_timeout'] config_file = module.params['config_file'] - config_file = os.path.expanduser(os.path.expandvars(config_file)) if not mysqldb_found: module.fail_json(msg="the python mysqldb module is required") diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/messaging/rabbitmq_binding.py ansible-2.1.2.0/lib/ansible/modules/extras/messaging/rabbitmq_binding.py --- ansible-2.1.1.0/lib/ansible/modules/extras/messaging/rabbitmq_binding.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/messaging/rabbitmq_binding.py 2016-09-29 16:06:39.000000000 +0000 @@ -127,6 +127,11 @@ else: dest_type="e" + if module.params['routing_key'] == "": + props = "~" + else: + props = urllib.quote(module.params['routing_key'],'') + url = "http://%s:%s/api/bindings/%s/e/%s/%s/%s/%s" % ( module.params['login_host'], module.params['login_port'], @@ -134,7 +139,7 @@ urllib.quote(module.params['name'],''), dest_type, urllib.quote(module.params['destination'],''), - urllib.quote(module.params['routing_key'],'') + props ) # Check if exchange already exists diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/monitoring/zabbix_host.py ansible-2.1.2.0/lib/ansible/modules/extras/monitoring/zabbix_host.py --- ansible-2.1.1.0/lib/ansible/modules/extras/monitoring/zabbix_host.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/monitoring/zabbix_host.py 2016-09-29 16:06:39.000000000 +0000 @@ -414,7 +414,7 @@ module = AnsibleModule( argument_spec=dict( server_url=dict(type='str', required=True, aliases=['url']), - login_user=dict(rtype='str', equired=True), + login_user=dict(type='str', required=True), login_password=dict(type='str', required=True, no_log=True), host_name=dict(type='str', required=True), http_login_user=dict(type='str', required=False, default=None), diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/network/f5/bigip_pool_member.py ansible-2.1.2.0/lib/ansible/modules/extras/network/f5/bigip_pool_member.py --- ansible-2.1.1.0/lib/ansible/modules/extras/network/f5/bigip_pool_member.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/network/f5/bigip_pool_member.py 2016-09-29 16:06:39.000000000 +0000 @@ -415,7 +415,7 @@ if not module.check_mode: set_member_session_enabled_state(api, pool, address, port, session_state) result = {'changed': True} - elif session_state == 'disabled' and session_status != 'force_disabled': + elif session_state == 'disabled' and session_status != 'forced_disabled': if not module.check_mode: set_member_session_enabled_state(api, pool, address, port, session_state) result = {'changed': True} diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/network/nmcli.py ansible-2.1.2.0/lib/ansible/modules/extras/network/nmcli.py --- ansible-2.1.1.0/lib/ansible/modules/extras/network/nmcli.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/network/nmcli.py 2016-09-29 16:06:39.000000000 +0000 @@ -514,6 +514,12 @@ return setting_list # print "" + def bool_to_string(self, boolean): + if boolean: + return "yes" + else: + return "no" + def list_connection_info(self): # Ask the settings service for the list of connections it provides bus=dbus.SystemBus() @@ -602,7 +608,7 @@ cmd.append(self.gw6) if self.autoconnect is not None: cmd.append('autoconnect') - cmd.append(self.autoconnect) + cmd.append(self.bool_to_string(self.autoconnect)) return cmd def modify_connection_team(self): @@ -631,7 +637,7 @@ cmd.append(self.dns6) if self.autoconnect is not None: cmd.append('autoconnect') - cmd.append(self.autoconnect) + cmd.append(self.bool_to_string(self.autoconnect)) # Can't use MTU with team return cmd @@ -704,7 +710,7 @@ cmd.append(self.gw6) if self.autoconnect is not None: cmd.append('autoconnect') - cmd.append(self.autoconnect) + cmd.append(self.bool_to_string(self.autoconnect)) if self.mode is not None: cmd.append('mode') cmd.append(self.mode) @@ -751,7 +757,7 @@ cmd.append(self.dns6) if self.autoconnect is not None: cmd.append('autoconnect') - cmd.append(self.autoconnect) + cmd.append(self.bool_to_string(self.autoconnect)) return cmd def create_connection_bond_slave(self): @@ -820,7 +826,7 @@ cmd.append(self.gw6) if self.autoconnect is not None: cmd.append('autoconnect') - cmd.append(self.autoconnect) + cmd.append(self.bool_to_string(self.autoconnect)) return cmd def modify_connection_ethernet(self): @@ -855,7 +861,7 @@ cmd.append(self.mtu) if self.autoconnect is not None: cmd.append('autoconnect') - cmd.append(self.autoconnect) + cmd.append(self.bool_to_string(self.autoconnect)) return cmd def create_connection_bridge(self): @@ -964,7 +970,7 @@ # Parsing argument file module=AnsibleModule( argument_spec=dict( - autoconnect=dict(required=False, default=None, choices=['yes', 'no'], type='str'), + autoconnect=dict(required=False, default=None, type='bool'), state=dict(required=True, choices=['present', 'absent'], type='str'), conn_name=dict(required=True, type='str'), master=dict(required=False, default=None, type='str'), @@ -987,7 +993,7 @@ mtu=dict(required=False, default=None, type='str'), mac=dict(required=False, default=None, type='str'), # bridge specific vars - stp=dict(required=False, default='yes', choices=['yes', 'no'], type='str'), + stp=dict(required=False, default=True, type='bool'), priority=dict(required=False, default="128", type='str'), slavepriority=dict(required=False, default="32", type='str'), forwarddelay=dict(required=False, default="15", type='str'), diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/notification/campfire.py ansible-2.1.2.0/lib/ansible/modules/extras/notification/campfire.py --- ansible-2.1.1.0/lib/ansible/modules/extras/notification/campfire.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/notification/campfire.py 2016-09-29 16:06:39.000000000 +0000 @@ -117,14 +117,14 @@ # Send some audible notification if requested if notify: response, info = fetch_url(module, target_url, data=NSTR % cgi.escape(notify), headers=headers) - if info['status'] != 200: - module.fail_json(msg="unable to send msg: '%s', campfire api" - " returned error code: '%s'" % - (notify, info['status'])) + if info['status'] not in [200, 201]: + module.fail_json(msg="unable to send msg: '%s', campfire api" + " returned error code: '%s'" % + (notify, info['status'])) # Send the message response, info = fetch_url(module, target_url, data=MSTR %cgi.escape(msg), headers=headers) - if info['status'] != 200: + if info['status'] not in [200, 201]: module.fail_json(msg="unable to send msg: '%s', campfire api" " returned error code: '%s'" % (msg, info['status'])) diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/notification/twilio.py ansible-2.1.2.0/lib/ansible/modules/extras/notification/twilio.py --- ansible-2.1.1.0/lib/ansible/modules/extras/notification/twilio.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/notification/twilio.py 2016-09-29 16:06:39.000000000 +0000 @@ -161,8 +161,12 @@ for number in to_number: r, info = post_twilio_api(module, account_sid, auth_token, msg, from_number, number, media_url) - if info['status'] != 200: - module.fail_json(msg="unable to send message to %s" % number) + if info['status'] not in [200, 201]: + body_message = "unknown error" + if 'body' in info: + body = json.loads(info['body']) + body_message = body['message'] + module.fail_json(msg="unable to send message to %s: %s" % (number, body_message)) module.exit_json(msg=msg, changed=False) diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/packaging/elasticsearch_plugin.py ansible-2.1.2.0/lib/ansible/modules/extras/packaging/elasticsearch_plugin.py --- ansible-2.1.1.0/lib/ansible/modules/extras/packaging/elasticsearch_plugin.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/packaging/elasticsearch_plugin.py 2016-09-29 16:06:39.000000000 +0000 @@ -39,7 +39,7 @@ description: - Desired state of a plugin. required: False - choices: [present, absent] + choices: ["present", "absent"] default: present url: description: @@ -92,6 +92,10 @@ - elasticsearch_plugin: state=absent name="mobz/elasticsearch-head" ''' +PACKAGE_STATE_MAP = dict( + present="install", + absent="remove" +) def parse_plugin_repo(string): elements = string.split("/") @@ -111,11 +115,9 @@ return repo - def is_plugin_present(plugin_dir, working_dir): return os.path.isdir(os.path.join(working_dir, plugin_dir)) - def parse_error(string): reason = "reason: " try: @@ -123,18 +125,49 @@ except ValueError: return string +def install_plugin(module, plugin_bin, plugin_name, version, url, proxy_host, proxy_port, timeout): + cmd_args = [plugin_bin, PACKAGE_STATE_MAP["present"], plugin_name] -def main(): + if version: + name = name + '/' + version - package_state_map = dict( - present="install", - absent="remove" - ) + if proxy_host and proxy_port: + cmd_args.append("-DproxyHost=%s -DproxyPort=%s" % (proxy_host, proxy_port)) + + if url: + cmd_args.append("--url %s" % url) + + if timeout: + cmd_args.append("--timeout %s" % timeout) + cmd = " ".join(cmd_args) + + rc, out, err = module.run_command(cmd) + + if rc != 0: + reason = parse_error(out) + module.fail_json(msg=reason) + + return True, cmd, out, err + +def remove_plugin(module, plugin_bin, plugin_name): + cmd_args = [plugin_bin, PACKAGE_STATE_MAP["absent"], parse_plugin_repo(plugin_name)] + + cmd = " ".join(cmd_args) + + rc, out, err = module.run_command(cmd) + + if rc != 0: + reason = parse_error(out) + module.fail_json(msg=reason) + + return True, cmd, out, err + +def main(): module = AnsibleModule( argument_spec=dict( name=dict(required=True), - state=dict(default="present", choices=package_state_map.keys()), + state=dict(default="present", choices=PACKAGE_STATE_MAP.keys()), url=dict(default=None), timeout=dict(default="1m"), plugin_bin=dict(default="/usr/share/elasticsearch/bin/plugin", type="path"), @@ -145,46 +178,31 @@ ) ) - name = module.params["name"] - state = module.params["state"] - url = module.params["url"] - timeout = module.params["timeout"] - plugin_bin = module.params["plugin_bin"] - plugin_dir = module.params["plugin_dir"] - proxy_host = module.params["proxy_host"] - proxy_port = module.params["proxy_port"] - version = module.params["version"] + name = module.params["name"] + state = module.params["state"] + url = module.params["url"] + timeout = module.params["timeout"] + plugin_bin = module.params["plugin_bin"] + plugin_dir = module.params["plugin_dir"] + proxy_host = module.params["proxy_host"] + proxy_port = module.params["proxy_port"] + version = module.params["version"] present = is_plugin_present(parse_plugin_repo(name), plugin_dir) # skip if the state is correct if (present and state == "present") or (state == "absent" and not present): - module.exit_json(changed=False, name=name) - - if (version): - name = name + '/' + version - - cmd_args = [plugin_bin, package_state_map[state], name] - - if proxy_host and proxy_port: - cmd_args.append("-DproxyHost=%s -DproxyPort=%s" % proxy_host, proxy_port) - - if url: - cmd_args.append("--url %s" % url) - - if timeout: - cmd_args.append("--timeout %s" % timeout) + module.exit_json(changed=False, name=name, state=state) - cmd = " ".join(cmd_args) + if state == "present": + changed, cmd, out, err = install_plugin(module, plugin_bin, name, version, url, proxy_host, proxy_port, timeout) - rc, out, err = module.run_command(cmd) - - if rc != 0: - reason = parse_error(out) - module.fail_json(msg=reason) + elif state == "absent": + changed, cmd, out, err = remove_plugin(module, plugin_bin, name) - module.exit_json(changed=True, cmd=cmd, name=name, state=state, url=url, timeout=timeout, stdout=out, stderr=err) + module.exit_json(changed=changed, cmd=cmd, name=name, state=state, url=url, timeout=timeout, stdout=out, stderr=err) from ansible.module_utils.basic import * -main() +if __name__ == '__main__': + main() diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/packaging/language/npm.py ansible-2.1.2.0/lib/ansible/modules/extras/packaging/language/npm.py --- ansible-2.1.1.0/lib/ansible/modules/extras/packaging/language/npm.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/packaging/language/npm.py 2016-09-29 16:06:39.000000000 +0000 @@ -253,7 +253,10 @@ elif state == 'latest': installed, missing = npm.list() outdated = npm.list_outdated() - if len(missing) or len(outdated): + if len(missing): + changed = True + npm.install() + if len(outdated): changed = True npm.update() else: #absent diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/packaging/os/apk.py ansible-2.1.2.0/lib/ansible/modules/extras/packaging/os/apk.py --- ansible-2.1.1.0/lib/ansible/modules/extras/packaging/os/apk.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/packaging/os/apk.py 2016-09-29 16:06:39.000000000 +0000 @@ -52,6 +52,8 @@ required: false default: no choices: [ "yes", "no" ] +notes: + - '"name" and "upgrade" are mutually exclusive.' ''' EXAMPLES = ''' @@ -181,6 +183,7 @@ upgrade = dict(default='no', type='bool'), ), required_one_of = [['name', 'update_cache', 'upgrade']], + mutually_exclusive = [['name', 'upgrade']], supports_check_mode = True ) diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/shippable.yml ansible-2.1.2.0/lib/ansible/modules/extras/shippable.yml --- ansible-2.1.1.0/lib/ansible/modules/extras/shippable.yml 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/shippable.yml 2016-09-29 16:06:39.000000000 +0000 @@ -0,0 +1,41 @@ +language: python + +env: + matrix: + - TEST=none + +matrix: + exclude: + - env: TEST=none + include: + - env: TEST=sanity INSTALL_DEPS=1 +build: + pre_ci_boot: + options: "--privileged=false --net=bridge" + ci: + - test/utils/shippable/ci.sh + +integrations: + notifications: + - integrationName: email + type: email + on_success: never + on_failure: never + on_start: never + on_pull_request: never + - integrationName: irc + type: irc + recipients: + - "chat.freenode.net#ansible-notices" + on_success: change + on_failure: always + on_start: never + on_pull_request: always + - integrationName: slack + type: slack + recipients: + - "#shippable" + on_success: change + on_failure: always + on_start: never + on_pull_request: never diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/system/debconf.py ansible-2.1.2.0/lib/ansible/modules/extras/system/debconf.py --- ansible-2.1.1.0/lib/ansible/modules/extras/system/debconf.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/system/debconf.py 2016-09-29 16:06:39.000000000 +0000 @@ -109,6 +109,11 @@ if unseen: cmd.append('-u') + if vtype == 'boolean': + if value == 'True': + value = 'true' + elif value == 'False': + value = 'false' data = ' '.join([pkg, question, vtype, value]) return module.run_command(cmd, data=data) diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/system/pam_limits.py ansible-2.1.2.0/lib/ansible/modules/extras/system/pam_limits.py --- ansible-2.1.1.0/lib/ansible/modules/extras/system/pam_limits.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/system/pam_limits.py 2016-09-29 16:06:39.000000000 +0000 @@ -210,7 +210,7 @@ if use_max: if value.isdigit() and actual_value.isdigit(): - new_value = max(int(value), int(actual_value)) + new_value = str(max(int(value), int(actual_value))) elif actual_value_unlimited: new_value = actual_value else: @@ -218,7 +218,7 @@ if use_min: if value.isdigit() and actual_value.isdigit(): - new_value = min(int(value), int(actual_value)) + new_value = str(min(int(value), int(actual_value))) elif value_unlimited: new_value = actual_value else: @@ -227,7 +227,7 @@ # Change line only if value has changed if new_value != actual_value: changed = True - new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + str(new_value) + new_comment + "\n" + new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + new_value + new_comment + "\n" message = new_limit nf.write(new_limit) else: @@ -238,7 +238,7 @@ if not found: changed = True - new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + str(new_value) + new_comment + "\n" + new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + new_value + new_comment + "\n" message = new_limit nf.write(new_limit) diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/system/ufw.py ansible-2.1.2.0/lib/ansible/modules/extras/system/ufw.py --- ansible-2.1.1.0/lib/ansible/modules/extras/system/ufw.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/system/ufw.py 2016-09-29 16:06:39.000000000 +0000 @@ -225,7 +225,7 @@ if len(commands) < 1: module.fail_json(msg="Not any of the command arguments %s given" % commands) - if('interface' in params and 'direction' not in params): + if(params['interface'] is not None and params['direction'] is None): module.fail_json(msg="Direction must be specified when creating a rule on an interface") # Ensure ufw is available @@ -260,10 +260,11 @@ cmd.append([module.boolean(params['route']), 'route']) cmd.append([params['insert'], "insert %s" % params['insert']]) cmd.append([value]) + cmd.append([params['direction'], "%s" % params['direction']]) + cmd.append([params['interface'], "on %s" % params['interface']]) cmd.append([module.boolean(params['log']), 'log']) - for (key, template) in [('direction', "%s" ), ('interface', "on %s" ), - ('from_ip', "from %s" ), ('from_port', "port %s" ), + for (key, template) in [('from_ip', "from %s" ), ('from_port', "port %s" ), ('to_ip', "to %s" ), ('to_port', "port %s" ), ('proto', "proto %s"), ('app', "app '%s'")]: diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/system/zfs.py ansible-2.1.2.0/lib/ansible/modules/extras/system/zfs.py --- ansible-2.1.1.0/lib/ansible/modules/extras/system/zfs.py 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/system/zfs.py 2016-09-29 16:06:39.000000000 +0000 @@ -185,8 +185,9 @@ if source == 'local': properties[prop] = value # Add alias for enhanced sharing properties - properties['sharenfs'] = properties.get('share.nfs', None) - properties['sharesmb'] = properties.get('share.smb', None) + if self.enhanced_sharing: + properties['sharenfs'] = properties.get('share.nfs', None) + properties['sharesmb'] = properties.get('share.smb', None) return properties diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/test/utils/shippable/ci.sh ansible-2.1.2.0/lib/ansible/modules/extras/test/utils/shippable/ci.sh --- ansible-2.1.1.0/lib/ansible/modules/extras/test/utils/shippable/ci.sh 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/test/utils/shippable/ci.sh 2016-09-29 16:06:39.000000000 +0000 @@ -0,0 +1,7 @@ +#!/bin/bash -eux + +set -o pipefail + +source_root=$(python -c "from os import path; print(path.abspath(path.join(path.dirname('$0'), '../../..')))") + +"${source_root}/test/utils/shippable/${TEST}.sh" 2>&1 | gawk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0; fflush(); }' diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/test/utils/shippable/sanity.sh ansible-2.1.2.0/lib/ansible/modules/extras/test/utils/shippable/sanity.sh --- ansible-2.1.1.0/lib/ansible/modules/extras/test/utils/shippable/sanity.sh 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/test/utils/shippable/sanity.sh 2016-09-29 16:06:39.000000000 +0000 @@ -0,0 +1,21 @@ +#!/bin/bash -eux + +source_root=$(python -c "from os import path; print(path.abspath(path.join(path.dirname('$0'), '../../..')))") + +install_deps="${INSTALL_DEPS:-}" + +cd "${source_root}" + +if [ "${install_deps}" != "" ]; then + add-apt-repository ppa:fkrull/deadsnakes && apt-get update -qq && apt-get install python2.4 -qq + + pip install git+https://github.com/ansible/ansible.git@stable-2.1#egg=ansible + pip install git+https://github.com/sivel/ansible-testing.git#egg=ansible_testing +fi + +python2.4 -m compileall -fq -i "test/utils/shippable/sanity-test-python24.txt" +python2.4 -m compileall -fq -x "($(printf %s "$(< "test/utils/shippable/sanity-skip-python24.txt"))" | tr '\n' '|')" . +python2.6 -m compileall -fq . +python2.7 -m compileall -fq . + +ansible-validate-modules --exclude '/utilities/|/shippable(/|$)' . diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/test/utils/shippable/sanity-skip-python24.txt ansible-2.1.2.0/lib/ansible/modules/extras/test/utils/shippable/sanity-skip-python24.txt --- ansible-2.1.1.0/lib/ansible/modules/extras/test/utils/shippable/sanity-skip-python24.txt 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/test/utils/shippable/sanity-skip-python24.txt 2016-09-29 16:06:39.000000000 +0000 @@ -0,0 +1,9 @@ +/cloud/ +/clustering/consul.*.py +/clustering/znode.py +/database/influxdb/ +/monitoring/zabbix.*.py +/notification/pushbullet.py +/packaging/language/maven_artifact.py +/packaging/os/dnf.py +/packaging/os/layman.py diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/test-docs.sh ansible-2.1.2.0/lib/ansible/modules/extras/test-docs.sh --- ansible-2.1.1.0/lib/ansible/modules/extras/test-docs.sh 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/test-docs.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -#!/bin/sh -set -x - -CHECKOUT_DIR=".ansible-checkout" -MOD_REPO="$1" - -# Hidden file to avoid the module_formatter recursing into the checkout -git clone https://github.com/ansible/ansible "$CHECKOUT_DIR" -cd "$CHECKOUT_DIR" -git submodule update --init -rm -rf "lib/ansible/modules/$MOD_REPO" -ln -s "$TRAVIS_BUILD_DIR/" "lib/ansible/modules/$MOD_REPO" - -pip install -U Jinja2 PyYAML setuptools six pycrypto sphinx - -. ./hacking/env-setup -PAGER=/bin/cat bin/ansible-doc -l -if [ $? -ne 0 ] ; then - exit $? -fi -make -C docsite diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/.travis.yml ansible-2.1.2.0/lib/ansible/modules/extras/.travis.yml --- ansible-2.1.1.0/lib/ansible/modules/extras/.travis.yml 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/.travis.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -sudo: false -language: python -python: - - "2.7" -addons: - apt: - sources: - - deadsnakes - packages: - - python2.4 - - python2.6 -before_install: - - git config user.name "ansible" - - git config user.email "ansible@ansible.com" - - git fetch origin - - if [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then git rebase origin $TRAVIS_BRANCH; fi; -install: - - pip install git+https://github.com/ansible/ansible.git@stable-2.1#egg=ansible - - pip install git+https://github.com/sivel/ansible-testing.git#egg=ansible_testing -script: - - python2.4 -m compileall -fq -x 'cloud/|monitoring/zabbix.*\.py|/dnf\.py|/layman\.py|/maven_artifact\.py|clustering/(consul.*|znode)\.py|notification/pushbullet\.py|database/influxdb/influxdb.*\.py' . - - python2.6 -m compileall -fq . - - python2.7 -m compileall -fq . - - ansible-validate-modules --exclude 'cloud/cloudstack/cs_template\.py|cloud/centurylink/clc_aa_policy\.py|cloud/centurylink/clc_alert_policy\.py|cloud/centurylink/clc_blueprint_package\.py|cloud/centurylink/clc_firewall_policy\.py|cloud/centurylink/clc_group\.py|cloud/centurylink/clc_loadbalancer\.py|cloud/centurylink/clc_modify_server\.py|cloud/centurylink/clc_publicip\.py|cloud/centurylink/clc_server\.py|cloud/centurylink/clc_server_snapshot\.py|cloud/docker/docker_login\.py|messaging/rabbitmq_binding\.py|messaging/rabbitmq_exchange\.py|messaging/rabbitmq_queue\.py|monitoring/circonus_annotation\.py|network/snmp_facts\.py|notification/sns\.py|cloud/cloudstack/cs_template\.py' . - #- ./test-docs.sh extras diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/VERSION ansible-2.1.2.0/lib/ansible/modules/extras/VERSION --- ansible-2.1.1.0/lib/ansible/modules/extras/VERSION 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/VERSION 2016-09-29 16:06:39.000000000 +0000 @@ -1 +1 @@ -2.1.1.0 1 +2.1.2.0 1 diff -Nru ansible-2.1.1.0/lib/ansible/modules/extras/windows/win_chocolatey.ps1 ansible-2.1.2.0/lib/ansible/modules/extras/windows/win_chocolatey.ps1 --- ansible-2.1.1.0/lib/ansible/modules/extras/windows/win_chocolatey.ps1 2016-07-28 17:54:44.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/modules/extras/windows/win_chocolatey.ps1 2016-09-29 16:06:39.000000000 +0000 @@ -59,7 +59,12 @@ if ($ChocoAlreadyInstalled -eq $null) { #We need to install chocolatey - iex ((new-object net.webclient).DownloadString("https://chocolatey.org/install.ps1")) + $install_output = (new-object net.webclient).DownloadString("https://chocolatey.org/install.ps1") | powershell - + if ($LASTEXITCODE -ne 0) + { + Set-Attr $result "choco_bootstrap_output" $install_output + Fail-Json $result "Chocolatey bootstrap installation failed." + } $result.changed = $true $script:executable = "C:\ProgramData\chocolatey\bin\choco.exe" } @@ -67,7 +72,7 @@ { $script:executable = "choco.exe" - if ((choco --version) -lt '0.9.9') + if ([Version](choco --version) -lt [Version]'0.9.9') { Choco-Upgrade chocolatey } @@ -209,9 +214,14 @@ Choco-Upgrade -package $package -version $version -source $source -force $force ` -installargs $installargs -packageparams $packageparams ` -ignoredependencies $ignoredependencies + + return } - return + if (-not $force) + { + return + } } $cmd = "$executable install -dv -y $package" diff -Nru ansible-2.1.1.0/lib/ansible/module_utils/basic.py ansible-2.1.2.0/lib/ansible/module_utils/basic.py --- ansible-2.1.1.0/lib/ansible/module_utils/basic.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/module_utils/basic.py 2016-09-29 16:06:33.000000000 +0000 @@ -582,6 +582,19 @@ else: raise AnsibleFallbackNotFound +def _lenient_lowercase(lst): + """Lowercase elements of a list. + + If an element is not a string, pass it through untouched. + """ + lowered = [] + for value in lst: + try: + lowered.append(value.lower()) + except AttributeError: + lowered.append(value) + return lowered + class AnsibleFallbackNotFound(Exception): pass @@ -1331,9 +1344,28 @@ if type(choices) == list: if k in self.params: if self.params[k] not in choices: - choices_str=",".join([str(c) for c in choices]) - msg="value of %s must be one of: %s, got: %s" % (k, choices_str, self.params[k]) - self.fail_json(msg=msg) + # PyYaml converts certain strings to bools. If we can unambiguously convert back, do so before checking the value. If we can't figure this out, module author is responsible. + lowered_choices = None + if self.params[k] == 'False': + lowered_choices = _lenient_lowercase(choices) + FALSEY = frozenset(BOOLEANS_FALSE) + overlap = FALSEY.intersection(choices) + if len(overlap) == 1: + # Extract from a set + (self.params[k],) = overlap + + if self.params[k] == 'True': + if lowered_choices is None: + lowered_choices = _lenient_lowercase(choices) + TRUTHY = frozenset(BOOLEANS_TRUE) + overlap = TRUTHY.intersection(choices) + if len(overlap) == 1: + (self.params[k],) = overlap + + if self.params[k] not in choices: + choices_str=",".join([str(c) for c in choices]) + msg="value of %s must be one of: %s, got: %s" % (k, choices_str, self.params[k]) + self.fail_json(msg=msg) else: self.fail_json(msg="internal error: do not know how to interpret argument_spec") @@ -1451,7 +1483,7 @@ if isinstance(value, float): return value - if isinstance(value, basestring): + if isinstance(value, (bytes, unicode, int)): return float(value) raise TypeError('%s cannot be converted to a float' % type(value)) diff -Nru ansible-2.1.1.0/lib/ansible/module_utils/cloudstack.py ansible-2.1.2.0/lib/ansible/module_utils/cloudstack.py --- ansible-2.1.1.0/lib/ansible/module_utils/cloudstack.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/module_utils/cloudstack.py 2016-09-29 16:06:33.000000000 +0000 @@ -157,7 +157,17 @@ continue if key in current_dict: - if isinstance(current_dict[key], (int, long, float, complex)): + if isinstance(value, (int, float, long, complex)): + # ensure we compare the same type + if isinstance(value, int): + current_dict[key] = int(current_dict[key]) + elif isinstance(value, float): + current_dict[key] = float(current_dict[key]) + elif isinstance(value, long): + current_dict[key] = long(current_dict[key]) + elif isinstance(value, complex): + current_dict[key] = complex(current_dict[key]) + if value != current_dict[key]: return True else: diff -Nru ansible-2.1.1.0/lib/ansible/module_utils/eos.py ansible-2.1.2.0/lib/ansible/module_utils/eos.py --- ansible-2.1.1.0/lib/ansible/module_utils/eos.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/module_utils/eos.py 2016-09-29 16:06:33.000000000 +0000 @@ -1,20 +1,29 @@ -# -# (c) 2015 Peter Sprygada, -# -# 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 . +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c) 2015 Peter Sprygada, +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import re diff -Nru ansible-2.1.1.0/lib/ansible/module_utils/iosxr.py ansible-2.1.2.0/lib/ansible/module_utils/iosxr.py --- ansible-2.1.1.0/lib/ansible/module_utils/iosxr.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/module_utils/iosxr.py 2016-09-29 16:06:33.000000000 +0000 @@ -1,20 +1,29 @@ -# -# (c) 2015 Peter Sprygada, -# -# 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 . +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c) 2015 Peter Sprygada, +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import re diff -Nru ansible-2.1.1.0/lib/ansible/module_utils/netcfg.py ansible-2.1.2.0/lib/ansible/module_utils/netcfg.py --- ansible-2.1.1.0/lib/ansible/module_utils/netcfg.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/module_utils/netcfg.py 2016-09-29 16:06:33.000000000 +0000 @@ -1,20 +1,29 @@ -# -# (c) 2015 Peter Sprygada, -# -# 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 . +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c) 2015 Peter Sprygada, +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import re diff -Nru ansible-2.1.1.0/lib/ansible/module_utils/urls.py ansible-2.1.2.0/lib/ansible/module_utils/urls.py --- ansible-2.1.1.0/lib/ansible/module_utils/urls.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/module_utils/urls.py 2016-09-29 16:06:33.000000000 +0000 @@ -631,6 +631,13 @@ use_proxy = self.detect_no_proxy(req.get_full_url()) if not use_proxy: + try: + # cleanup the temp file created, don't worry + # if it fails for some reason + os.remove(tmp_ca_cert_path) + except: + pass + # ignore proxy settings for this host request return req diff -Nru ansible-2.1.1.0/lib/ansible/parsing/vault/__init__.py ansible-2.1.2.0/lib/ansible/parsing/vault/__init__.py --- ansible-2.1.1.0/lib/ansible/parsing/vault/__init__.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/parsing/vault/__init__.py 2016-09-29 16:06:33.000000000 +0000 @@ -481,7 +481,7 @@ # VaultFile a context manager instead (implement __enter__ and __exit__) def __del__(self): self.filehandle.close() - os.unlink(self.tmplfile) + os.unlink(self.tmpfile) def is_encrypted(self): peak = self.filehandle.readline() diff -Nru ansible-2.1.1.0/lib/ansible/playbook/base.py ansible-2.1.2.0/lib/ansible/playbook/base.py --- ansible-2.1.1.0/lib/ansible/playbook/base.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/playbook/base.py 2016-09-29 16:06:33.000000000 +0000 @@ -86,6 +86,19 @@ # and init vars, avoid using defaults in field declaration as it lives across plays self.vars = dict() + def dump_me(self, depth=0): + if depth == 0: + print("DUMPING OBJECT ------------------------------------------------------") + print("%s- %s (%s, id=%s)" % (" " * depth, self.__class__.__name__, self, id(self))) + if hasattr(self, '_block') and self.__class__.__name__ == 'Task' and self._block: + self._block.dump_me(depth+2) + for attr_name in ('_parent_block', '_task_include'): + if hasattr(self, attr_name): + attr = getattr(self, attr_name) + if attr is not None: + attr.dump_me(depth+2) + if hasattr(self, '_play') and self._play: + self._play.dump_me(depth+2) # The following three functions are used to programatically define data # descriptors (aka properties) for the Attributes of all of the playbook diff -Nru ansible-2.1.1.0/lib/ansible/playbook/block.py ansible-2.1.2.0/lib/ansible/playbook/block.py --- ansible-2.1.1.0/lib/ansible/playbook/block.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/playbook/block.py 2016-09-29 16:06:33.000000000 +0000 @@ -383,3 +383,24 @@ def has_tasks(self): return len(self.block) > 0 or len(self.rescue) > 0 or len(self.always) > 0 + def get_include_params(self): + if self._parent: + return self._parent.get_include_params() + else: + return dict() + + def all_parents_static(self): + ''' + Determine if all of the parents of this block were statically loaded + or not. Since Task/TaskInclude objects may be in the chain, they simply + call their parents all_parents_static() method. Only Block objects in + the chain check the statically_loaded value of the parent. + ''' + from ansible.playbook.task_include import TaskInclude + if self._task_include and not self._task_include.statically_loaded: + return False + elif self._parent_block: + return self._parent_block.all_parents_static() + + return True + diff -Nru ansible-2.1.1.0/lib/ansible/playbook/helpers.py ansible-2.1.2.0/lib/ansible/playbook/helpers.py --- ansible-2.1.1.0/lib/ansible/playbook/helpers.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/playbook/helpers.py 2016-09-29 16:06:33.000000000 +0000 @@ -119,38 +119,41 @@ else: is_static = C.DEFAULT_TASK_INCLUDES_STATIC or \ (use_handlers and C.DEFAULT_HANDLER_INCLUDES_STATIC) or \ - (not templar._contains_vars(t.args['_raw_params']) and not t.loop) + (not templar._contains_vars(t.args['_raw_params']) and t.all_parents_static() and not t.loop) if is_static: if t.loop is not None: raise AnsibleParserError("You cannot use 'static' on an include with a loop", obj=task_ds) - # FIXME: all of this code is very similar (if not identical) to that in - # plugins/strategy/__init__.py, and should be unified to avoid - # patches only being applied to one or the other location - if task_include: - # handle relative includes by walking up the list of parent include - # tasks and checking the relative result to see if it exists - parent_include = task_include - cumulative_path = None - while parent_include is not None: - parent_include_dir = templar.template(os.path.dirname(parent_include.args.get('_raw_params'))) - if cumulative_path is None: - cumulative_path = parent_include_dir - elif not os.path.isabs(cumulative_path): - cumulative_path = os.path.join(parent_include_dir, cumulative_path) - include_target = templar.template(t.args['_raw_params']) - if t._role: - new_basedir = os.path.join(t._role._role_path, 'tasks', cumulative_path) - include_file = loader.path_dwim_relative(new_basedir, 'tasks', include_target) - else: - include_file = loader.path_dwim_relative(loader.get_basedir(), cumulative_path, include_target) + # we set a flag to indicate this include was static + t.statically_loaded = True - if os.path.exists(include_file): - break - else: - parent_include = parent_include._task_include - else: + # handle relative includes by walking up the list of parent include + # tasks and checking the relative result to see if it exists + parent_include = task_include + cumulative_path = None + + found = False + while parent_include is not None: + parent_include_dir = templar.template(os.path.dirname(parent_include.args.get('_raw_params'))) + if cumulative_path is None: + cumulative_path = parent_include_dir + elif not os.path.isabs(cumulative_path): + cumulative_path = os.path.join(parent_include_dir, cumulative_path) + include_target = templar.template(t.args['_raw_params']) + if t._role: + new_basedir = os.path.join(t._role._role_path, 'tasks', cumulative_path) + include_file = loader.path_dwim_relative(new_basedir, 'tasks', include_target) + else: + include_file = loader.path_dwim_relative(loader.get_basedir(), cumulative_path, include_target) + + if os.path.exists(include_file): + found = True + break + else: + parent_include = parent_include._task_include + + if not found: try: include_target = templar.template(t.args['_raw_params']) except AnsibleUndefinedVariable as e: @@ -176,6 +179,12 @@ return [] elif not isinstance(data, list): raise AnsibleError("included task files must contain a list of tasks", obj=data) + + # since we can't send callbacks here, we display a message directly in + # the same fashion used by the on_include callback. We also do it here, + # because the recursive nature of helper methods means we may be loading + # nested includes, and we want the include order printed correctly + display.display("statically included: %s" % include_file, color=C.COLOR_SKIP) except AnsibleFileNotFound as e: if t.static or \ C.DEFAULT_TASK_INCLUDES_STATIC or \ @@ -227,7 +236,6 @@ b.tags = list(set(b.tags).union(tags)) # END FIXME - # FIXME: send callback here somehow... # FIXME: handlers shouldn't need this special handling, but do # right now because they don't iterate blocks correctly if use_handlers: diff -Nru ansible-2.1.1.0/lib/ansible/playbook/play.py ansible-2.1.2.0/lib/ansible/playbook/play.py --- ansible-2.1.1.0/lib/ansible/playbook/play.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/playbook/play.py 2016-09-29 16:06:33.000000000 +0000 @@ -136,28 +136,6 @@ return super(Play, self).preprocess_data(ds) - def _load_hosts(self, attr, ds): - ''' - Loads the hosts from the given datastructure, which might be a list - or a simple string. We also switch integers in this list back to strings, - as the YAML parser will turn things that look like numbers into numbers. - ''' - - if isinstance(ds, (string_types, int)): - ds = [ ds ] - - if not isinstance(ds, list): - raise AnsibleParserError("'hosts' must be specified as a list or a single pattern", obj=ds) - - # YAML parsing of things that look like numbers may have - # resulted in integers showing up in the list, so convert - # them back to strings to prevent problems - for idx,item in enumerate(ds): - if isinstance(item, int): - ds[idx] = "%s" % item - - return ds - def _load_tasks(self, attr, ds): ''' Loads a list of blocks from a list which may be mixed tasks/blocks. diff -Nru ansible-2.1.1.0/lib/ansible/playbook/task_include.py ansible-2.1.2.0/lib/ansible/playbook/task_include.py --- ansible-2.1.1.0/lib/ansible/playbook/task_include.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/playbook/task_include.py 2016-09-29 16:06:33.000000000 +0000 @@ -43,11 +43,20 @@ _static = FieldAttribute(isa='bool', default=None) + def __init__(self, block=None, role=None, task_include=None): + super(TaskInclude, self).__init__(block=block, role=role, task_include=task_include) + self.statically_loaded = False + @staticmethod def load(data, block=None, role=None, task_include=None, variable_manager=None, loader=None): t = TaskInclude(block=block, role=role, task_include=task_include) return t.load_data(data, variable_manager=variable_manager, loader=loader) + def copy(self, exclude_block=False): + new_me = super(TaskInclude, self).copy(exclude_block=exclude_block) + new_me.statically_loaded = self.statically_loaded + return new_me + def get_vars(self): ''' We override the parent Task() classes get_vars here because diff -Nru ansible-2.1.1.0/lib/ansible/playbook/task.py ansible-2.1.2.0/lib/ansible/playbook/task.py --- ansible-2.1.1.0/lib/ansible/playbook/task.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/playbook/task.py 2016-09-29 16:06:33.000000000 +0000 @@ -447,3 +447,42 @@ ''' return self._get_parent_attribute('any_errors_fatal') + def _get_attr_loop(self): + return self._attributes['loop'] + + def _get_attr_loop_control(self): + return self._attributes['loop_control'] + + def get_dep_chain(self): + if self._parent: + return self._parent.get_dep_chain() + else: + return None + + def get_search_path(self): + ''' + Return the list of paths you should search for files, in order. + This follows role/playbook dependency chain. + ''' + path_stack = [] + + dep_chain = self.get_dep_chain() + # inside role: add the dependency chain from current to dependant + if dep_chain: + path_stack.extend(reversed([x._role_path for x in dep_chain])) + + # add path of task itself, unless it is already in the list + task_dir = os.path.dirname(self.get_path()) + if task_dir not in path_stack: + path_stack.append(task_dir) + + return path_stack + + def all_parents_static(self): + if self._task_include and not self._task_include.statically_loaded: + return False + elif self._block: + return self._block.all_parents_static() + + return True + diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/assemble.py ansible-2.1.2.0/lib/ansible/plugins/action/assemble.py --- ansible-2.1.1.0/lib/ansible/plugins/action/assemble.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/assemble.py 2016-09-29 16:06:33.000000000 +0000 @@ -153,7 +153,7 @@ xfered = self._transfer_file(path, remote_path) # fix file permissions when the copy is done as a different user - self._fixup_perms(tmp, remote_user, recursive=True) + self._fixup_perms2((tmp, remote_path), remote_user) new_module_args.update( dict( src=xfered,)) diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/async.py ansible-2.1.2.0/lib/ansible/plugins/action/async.py --- ansible-2.1.1.0/lib/ansible/plugins/action/async.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/async.py 2016-09-29 16:06:33.000000000 +0000 @@ -70,17 +70,13 @@ args_data += '%s="%s" ' % (k, pipes.quote(to_unicode(v))) argsfile = self._transfer_data(self._connection._shell.join_path(tmp, 'arguments'), args_data) - self._fixup_perms(tmp, remote_user, execute=True, recursive=True) - # Only the following two files need to be executable but we'd have to - # make three remote calls if we wanted to just set them executable. - # There's not really a problem with marking too many of the temp files - # executable so we go ahead and mark them all as executable in the - # line above (the line above is needed in any case [although - # execute=False is okay if we uncomment the lines below] so that all - # the files are readable in case the remote_user and become_user are - # different and both unprivileged) - #self._fixup_perms(remote_module_path, remote_user, execute=True, recursive=False) - #self._fixup_perms(async_module_path, remote_user, execute=True, recursive=False) + remote_paths = tmp, remote_module_path, async_module_path + + # argsfile doesn't need to be executable, but this saves an extra call to the remote host + if argsfile: + remote_paths += argsfile, + + self._fixup_perms2(remote_paths, remote_user, execute=True) async_limit = self._task.async async_jid = str(random.randint(0, 999999999999)) diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/copy.py ansible-2.1.2.0/lib/ansible/plugins/action/copy.py --- ansible-2.1.1.0/lib/ansible/plugins/action/copy.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/copy.py 2016-09-29 16:06:33.000000000 +0000 @@ -217,8 +217,10 @@ # Define a remote directory that we will copy the file to. tmp_src = self._connection._shell.join_path(tmp, 'source') + remote_path = None + if not raw: - self._transfer_file(source_full, tmp_src) + remote_path = self._transfer_file(source_full, tmp_src) else: self._transfer_file(source_full, dest_file) @@ -227,7 +229,8 @@ self._loader.cleanup_tmp_file(source_full) # fix file permissions when the copy is done as a different user - self._fixup_perms(tmp, remote_user, recursive=True) + if remote_path: + self._fixup_perms2((tmp, remote_path), remote_user) if raw: # Continue to next iteration if raw is defined. diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/fetch.py ansible-2.1.2.0/lib/ansible/plugins/action/fetch.py --- ansible-2.1.1.0/lib/ansible/plugins/action/fetch.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/fetch.py 2016-09-29 16:06:33.000000000 +0000 @@ -64,7 +64,8 @@ remote_checksum = None if not self._play_context.become: # calculate checksum for the remote file, don't bother if using become as slurp will be used - remote_checksum = self._remote_checksum(source, all_vars=task_vars) + # Force remote_checksum to follow symlinks because fetch always follows symlinks + remote_checksum = self._remote_checksum(source, all_vars=task_vars, follow=True) # use slurp if permissions are lacking or privilege escalation is needed remote_data = None diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/include_vars.py ansible-2.1.2.0/lib/ansible/plugins/action/include_vars.py --- ansible-2.1.1.0/lib/ansible/plugins/action/include_vars.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/include_vars.py 2016-09-29 16:06:33.000000000 +0000 @@ -34,6 +34,8 @@ result = super(ActionModule, self).run(tmp, task_vars) source = self._task.args.get('_raw_params') + if source is None: + raise AnsibleError("No filename was specified to include.", self._task._ds) if self._task._role: source = self._loader.path_dwim_relative(self._task._role._role_path, 'vars', source) diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/__init__.py ansible-2.1.2.0/lib/ansible/plugins/action/__init__.py --- ansible-2.1.1.0/lib/ansible/plugins/action/__init__.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/__init__.py 2016-09-29 16:06:33.000000000 +0000 @@ -293,7 +293,29 @@ return remote_path - def _fixup_perms(self, remote_path, remote_user, execute=False, recursive=True): + def _fixup_perms(self, remote_path, remote_user, execute=True, recursive=True): + """ + We need the files we upload to be readable (and sometimes executable) + by the user being sudo'd to but we want to limit other people's access + (because the files could contain passwords or other private + information. + + Deprecated in favor of _fixup_perms2. Ansible code has been updated to + use _fixup_perms2. This code is maintained to provide partial support + for custom actions (non-recursive mode only). + + """ + + display.deprecated('_fixup_perms is deprecated. Use _fixup_perms2 instead.', version='2.4', removed=False) + + if recursive: + raise AnsibleError('_fixup_perms with recursive=True (the default) is no longer supported. ' + + 'Use _fixup_perms2 if support for previous releases is not required. ' + 'Otherwise use fixup_perms with recursive=False.') + + return self._fixup_perms2([remote_path], remote_user, execute) + + def _fixup_perms2(self, remote_paths, remote_user, execute=True): """ We need the files we upload to be readable (and sometimes executable) by the user being sudo'd to but we want to limit other people's access @@ -301,17 +323,17 @@ information. We achieve this in one of these ways: * If no sudo is performed or the remote_user is sudo'ing to - themselves, we don't have to change permisions. + themselves, we don't have to change permissions. * If the remote_user sudo's to a privileged user (for instance, root), we don't have to change permissions - * If the remote_user is a privileged user and sudo's to an - unprivileged user then we change the owner of the file to the - unprivileged user so they can read it. - * If the remote_user is an unprivieged user and we're sudo'ing to - a second unprivileged user then we attempt to grant the second - unprivileged user access via file system acls. - * If granting file system acls fails we can set the file to be world - readable so that the second unprivileged user can read the file. + * If the remote_user sudo's to an unprivileged user then we attempt to + grant the unprivileged user access via file system acls. + * If granting file system acls fails we try to change the owner of the + file with chown which only works in case the remote_user is + privileged or the remote systems allows chown calls by unprivileged + users (e.g. HP-UX) + * If the chown fails we can set the file to be world readable so that + the second unprivileged user can read the file. Since this could allow other users to get access to private information we only do this ansible is configured with "allow_world_readable_tmpfiles" in the ansible.cfg @@ -319,51 +341,39 @@ if self._connection._shell.SHELL_FAMILY == 'powershell': # This won't work on Powershell as-is, so we'll just completely skip until # we have a need for it, at which point we'll have to do something different. - return remote_path - - if remote_path is None: - # Sometimes code calls us naively -- it has a var which could - # contain a path to a tmp dir but doesn't know if it needs to - # exist or not. If there's no path, then there's no need for us - # to do work - display.debug('_fixup_perms called with remote_path==None. Sure this is correct?') - return remote_path + return remote_paths if self._play_context.become and self._play_context.become_user not in ('root', remote_user): # Unprivileged user that's different than the ssh user. Let's get # to work! - # Try chown'ing the file. This will only work if our SSH user has - # root privileges, but since we can't reliably determine that from - # the username (think "toor" on FreeBSD), let's just try first and - # apologize later: - res = self._remote_chown(remote_path, self._play_context.become_user, recursive=recursive) - if res['rc'] == 0: - # root can read things that don't have read bit but can't - # execute them without the execute bit, so we might need to - # set that even if we're root. We just ran chown successfully, - # so apparently we are root. + # Try to use file system acls to make the files readable for sudo'd + # user + if execute: + mode = 'rx' + else: + mode = 'rX' + + res = self._remote_set_user_facl(remote_paths, self._play_context.become_user, mode) + if res['rc'] != 0: + # File system acls failed; let's try to use chown next + # Set executable bit first as on some systems an + # unprivileged user can use chown if execute: - res = self._remote_chmod('u+x', remote_path, recursive=recursive) + res = self._remote_chmod(remote_paths, 'u+x') if res['rc'] != 0: raise AnsibleError('Failed to set file mode on remote temporary files (rc: {0}, err: {1})'.format(res['rc'], res['stderr'])) - elif remote_user == 'root': - raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as root. Unprivileged become user would be unable to read the file.') - else: - # Chown'ing failed. We're probably lacking root privileges; let's try something else. - if execute: - mode = 'rx' - else: - mode = 'rX' - # Try to use fs acls to solve this problem - res = self._remote_set_user_facl(remote_path, self._play_context.become_user, mode, recursive=recursive, sudoable=False) - if res['rc'] != 0: + res = self._remote_chown(remote_paths, self._play_context.become_user) + if res['rc'] != 0 and remote_user == 'root': + # chown failed even if remove_user is root + raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as root. Unprivileged become user would be unable to read the file.') + elif res['rc'] != 0: if C.ALLOW_WORLD_READABLE_TMPFILES: - # fs acls failed -- do things this insecure way only - # if the user opted in in the config file + # chown and fs acls failed -- do things this insecure + # way only if the user opted in in the config file display.warning('Using world-readable permissions for temporary files Ansible needs to create when becoming an unprivileged user which may be insecure. For information on securing this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user') - res = self._remote_chmod('a+%s' % mode, remote_path, recursive=recursive) + res = self._remote_chmod(remote_paths, 'a+%s' % mode) if res['rc'] != 0: raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], res['stderr'])) else: @@ -372,33 +382,33 @@ # Can't depend on the file being transferred with execute # permissions. Only need user perms because no become was # used here - res = self._remote_chmod('u+x', remote_path, recursive=recursive) + res = self._remote_chmod(remote_paths, 'u+x') if res['rc'] != 0: raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], res['stderr'])) - return remote_path + return remote_paths - def _remote_chmod(self, mode, path, recursive=True, sudoable=False): + def _remote_chmod(self, paths, mode, sudoable=False): ''' Issue a remote chmod command ''' - cmd = self._connection._shell.chmod(mode, path, recursive=recursive) + cmd = self._connection._shell.chmod(paths, mode) res = self._low_level_execute_command(cmd, sudoable=sudoable) return res - def _remote_chown(self, path, user, group=None, recursive=True, sudoable=False): + def _remote_chown(self, paths, user, sudoable=False): ''' Issue a remote chown command ''' - cmd = self._connection._shell.chown(path, user, group, recursive=recursive) + cmd = self._connection._shell.chown(paths, user) res = self._low_level_execute_command(cmd, sudoable=sudoable) return res - def _remote_set_user_facl(self, path, user, mode, recursive=True, sudoable=False): + def _remote_set_user_facl(self, paths, user, mode, sudoable=False): ''' Issue a remote call to setfacl ''' - cmd = self._connection._shell.set_user_facl(path, user, mode, recursive=recursive) + cmd = self._connection._shell.set_user_facl(paths, user, mode) res = self._low_level_execute_command(cmd, sudoable=sudoable) return res @@ -605,7 +615,7 @@ # the remote system, which can be read and parsed by the module args_data = "" for k,v in iteritems(module_args): - args_data += '%s="%s" ' % (k, pipes.quote(text_type(v))) + args_data += '%s=%s ' % (k, pipes.quote(text_type(v))) self._transfer_data(args_file_path, args_data) elif module_style == 'non_native_want_json': self._transfer_data(args_file_path, json.dumps(module_args)) @@ -613,9 +623,17 @@ environment_string = self._compute_environment_string() + remote_files = None + + if args_file_path: + remote_files = tmp, remote_module_path, args_file_path + elif remote_module_path: + remote_files = tmp, remote_module_path + # Fix permissions of the tmp path and tmp files. This should be # called after all files have been transferred. - self._fixup_perms(tmp, remote_user, recursive=True) + if remote_files: + self._fixup_perms2(remote_files, remote_user) cmd = "" in_data = None @@ -667,9 +685,10 @@ def _parse_returned_data(self, res): try: data = json.loads(self._filter_non_json_lines(res.get('stdout', u''))) + data['_ansible_parsed'] = True except ValueError: # not valid json, lets try to capture error - data = dict(failed=True, parsed=False) + data = dict(failed=True, _ansible_parsed=False) data['msg'] = "MODULE FAILURE" data['module_stdout'] = res.get('stdout', u'') if 'stderr' in res: @@ -715,7 +734,14 @@ cmd = executable + ' -c ' + pipes.quote(cmd) display.debug("_low_level_execute_command(): executing: %s" % (cmd,)) - rc, stdout, stderr = self._connection.exec_command(cmd, in_data=in_data, sudoable=sudoable) + + # Change directory to basedir of task for command execution + cwd = os.getcwd() + os.chdir(self._loader.get_basedir()) + try: + rc, stdout, stderr = self._connection.exec_command(cmd, in_data=in_data, sudoable=sudoable) + finally: + os.chdir(cwd) # stdout and stderr may be either a file-like or a bytes object. # Convert either one to a text type diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/net_template.py ansible-2.1.2.0/lib/ansible/plugins/action/net_template.py --- ansible-2.1.1.0/lib/ansible/plugins/action/net_template.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/net_template.py 2016-09-29 16:06:33.000000000 +0000 @@ -75,6 +75,9 @@ def _handle_template(self): src = self._task.args.get('src') + if not src: + return + working_path = self._get_working_path() if os.path.isabs(src) or urlparse.urlsplit('src').scheme: diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/patch.py ansible-2.1.2.0/lib/ansible/plugins/action/patch.py --- ansible-2.1.1.0/lib/ansible/plugins/action/patch.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/patch.py 2016-09-29 16:06:33.000000000 +0000 @@ -59,7 +59,7 @@ tmp_src = self._connection._shell.join_path(tmp, os.path.basename(src)) self._transfer_file(src, tmp_src) - self._fixup_perms(tmp, remote_user, recursive=True) + self._fixup_perms2((tmp, tmp_src), remote_user) new_module_args = self._task.args.copy() new_module_args.update( diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/script.py ansible-2.1.2.0/lib/ansible/plugins/action/script.py --- ansible-2.1.1.0/lib/ansible/plugins/action/script.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/script.py 2016-09-29 16:06:33.000000000 +0000 @@ -79,7 +79,7 @@ self._transfer_file(source, tmp_src) # set file permissions, more permissive when the copy is done as a different user - self._fixup_perms(tmp, remote_user, execute=True, recursive=True) + self._fixup_perms2((tmp, tmp_src), remote_user, execute=True) # add preparation steps to one ssh roundtrip executing the script env_string = self._compute_environment_string() diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/synchronize.py ansible-2.1.2.0/lib/ansible/plugins/action/synchronize.py --- ansible-2.1.1.0/lib/ansible/plugins/action/synchronize.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/synchronize.py 2016-09-29 16:06:33.000000000 +0000 @@ -272,10 +272,7 @@ if not dest_is_local: # Private key handling - if use_delegate: - private_key = task_vars.get('ansible_ssh_private_key_file') or self._play_context.private_key_file - else: - private_key = task_vars.get('ansible_ssh_private_key_file') or self._play_context.private_key_file + private_key = self._play_context.private_key_file if private_key is not None: private_key = os.path.expanduser(private_key) diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/template.py ansible-2.1.2.0/lib/ansible/plugins/action/template.py --- ansible-2.1.1.0/lib/ansible/plugins/action/template.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/template.py 2016-09-29 16:06:33.000000000 +0000 @@ -164,7 +164,7 @@ xfered = self._transfer_data(self._connection._shell.join_path(tmp, 'source'), resultant) # fix file permissions when the copy is done as a different user - self._fixup_perms(tmp, remote_user, recursive=True) + self._fixup_perms2((tmp, xfered), remote_user) # run the copy module new_module_args.update( diff -Nru ansible-2.1.1.0/lib/ansible/plugins/action/unarchive.py ansible-2.1.2.0/lib/ansible/plugins/action/unarchive.py --- ansible-2.1.1.0/lib/ansible/plugins/action/unarchive.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/action/unarchive.py 2016-09-29 16:06:33.000000000 +0000 @@ -93,7 +93,7 @@ if copy: # fix file permissions when the copy is done as a different user - self._fixup_perms(tmp, remote_user, recursive=True) + self._fixup_perms2((tmp, tmp_src), remote_user) # Build temporary module_args. new_module_args = self._task.args.copy() new_module_args.update( diff -Nru ansible-2.1.1.0/lib/ansible/plugins/connection/local.py ansible-2.1.2.0/lib/ansible/plugins/connection/local.py --- ansible-2.1.1.0/lib/ansible/plugins/connection/local.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/connection/local.py 2016-09-29 16:06:33.000000000 +0000 @@ -69,7 +69,6 @@ executable = C.DEFAULT_EXECUTABLE.split()[0] if C.DEFAULT_EXECUTABLE else None display.vvv(u"EXEC {0}".format(cmd), host=self._play_context.remote_addr) - # FIXME: cwd= needs to be set to the basedir of the playbook display.debug("opening command with Popen()") if isinstance(cmd, (text_type, binary_type)): diff -Nru ansible-2.1.1.0/lib/ansible/plugins/lookup/hashi_vault.py ansible-2.1.2.0/lib/ansible/plugins/lookup/hashi_vault.py --- ansible-2.1.1.0/lib/ansible/plugins/lookup/hashi_vault.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/lookup/hashi_vault.py 2016-09-29 16:06:33.000000000 +0000 @@ -45,7 +45,7 @@ try: import hvac except ImportError: - AnsibleError("Please pip install hvac to use this module") + raise AnsibleError("Please pip install hvac to use this module") self.url = kwargs.pop('url') self.secret = kwargs.pop('secret') diff -Nru ansible-2.1.1.0/lib/ansible/plugins/shell/__init__.py ansible-2.1.2.0/lib/ansible/plugins/shell/__init__.py --- ansible-2.1.1.0/lib/ansible/plugins/shell/__init__.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/shell/__init__.py 2016-09-29 16:06:33.000000000 +0000 @@ -56,45 +56,25 @@ def path_has_trailing_slash(self, path): return path.endswith('/') - def chmod(self, mode, path, recursive=True): - path = pipes.quote(path) - cmd = ['chmod'] - - if recursive: - cmd.append('-R') # many chmods require -R before file list - - cmd.extend([mode, path]) + def chmod(self, paths, mode): + cmd = ['chmod', mode] + cmd.extend(paths) + cmd = [pipes.quote(c) for c in cmd] return ' '.join(cmd) - def chown(self, path, user, group=None, recursive=True): - path = pipes.quote(path) - user = pipes.quote(user) - - cmd = ['chown'] - - if recursive: - cmd.append('-R') # many chowns require -R before file list - - if group is None: - cmd.extend([user, path]) - else: - group = pipes.quote(group) - cmd.extend(['%s:%s' % (user, group), path]) + def chown(self, paths, user): + cmd = ['chown', user] + cmd.extend(paths) + cmd = [pipes.quote(c) for c in cmd] return ' '.join(cmd) - def set_user_facl(self, path, user, mode, recursive=True): + def set_user_facl(self, paths, user, mode): """Only sets acls for users as that's really all we need""" - path = pipes.quote(path) - mode = pipes.quote(mode) - user = pipes.quote(user) - cmd = ['setfacl', '-m', 'u:%s:%s' % (user, mode)] - if recursive: - cmd = ['find', path, '-exec'] + cmd + ["'{}'", "'+'"] - else: - cmd.append(path) + cmd.extend(paths) + cmd = [pipes.quote(c) for c in cmd] return ' '.join(cmd) diff -Nru ansible-2.1.1.0/lib/ansible/plugins/shell/powershell.py ansible-2.1.2.0/lib/ansible/plugins/shell/powershell.py --- ansible-2.1.1.0/lib/ansible/plugins/shell/powershell.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/shell/powershell.py 2016-09-29 16:06:33.000000000 +0000 @@ -66,13 +66,13 @@ path = self._unquote(path) return path.endswith('/') or path.endswith('\\') - def chmod(self, mode, path, recursive=True): + def chmod(self, paths, mode): raise NotImplementedError('chmod is not implemented for Powershell') - def chown(self, path, user, group=None, recursive=True): + def chown(self, paths, user): raise NotImplementedError('chown is not implemented for Powershell') - def set_user_facl(self, path, user, mode, recursive=True): + def set_user_facl(self, paths, user, mode): raise NotImplementedError('set_user_facl is not implemented for Powershell') def remove(self, path, recurse=False): diff -Nru ansible-2.1.1.0/lib/ansible/plugins/strategy/debug.py ansible-2.1.2.0/lib/ansible/plugins/strategy/debug.py --- ansible-2.1.1.0/lib/ansible/plugins/strategy/debug.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/strategy/debug.py 2016-09-29 16:06:33.000000000 +0000 @@ -6,7 +6,7 @@ import pprint import sys -from ansible.plugins.strategy import linear +from ansible.plugins.strategy.linear import StrategyModule as LinearStrategyModule try: from __main__ import display @@ -25,7 +25,7 @@ self.result = result -class StrategyModule(linear.StrategyModule): +class StrategyModule(LinearStrategyModule): def __init__(self, tqm): self.curr_tqm = tqm super(StrategyModule, self).__init__(tqm) diff -Nru ansible-2.1.1.0/lib/ansible/plugins/strategy/free.py ansible-2.1.2.0/lib/ansible/plugins/strategy/free.py --- ansible-2.1.1.0/lib/ansible/plugins/strategy/free.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/strategy/free.py 2016-09-29 16:06:33.000000000 +0000 @@ -26,6 +26,7 @@ from ansible.plugins import action_loader from ansible.plugins.strategy import StrategyBase from ansible.template import Templar +from ansible.utils.unicode import to_unicode try: from __main__ import display @@ -107,6 +108,15 @@ templar = Templar(loader=self._loader, variables=task_vars) display.debug("done getting variables") + try: + task.name = to_unicode(templar.template(task.name, fail_on_undefined=False), nonstring='empty') + display.debug("done templating") + except: + # just ignore any errors during task name templating, + # we don't care if it just shows the raw name + display.debug("templating failed for some reason") + pass + run_once = templar.template(task.run_once) or action and getattr(action, 'BYPASS_HOST_LOOP', False) if run_once: if action and getattr(action, 'BYPASS_HOST_LOOP', False): diff -Nru ansible-2.1.1.0/lib/ansible/plugins/strategy/__init__.py ansible-2.1.2.0/lib/ansible/plugins/strategy/__init__.py --- ansible-2.1.1.0/lib/ansible/plugins/strategy/__init__.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/strategy/__init__.py 2016-09-29 16:06:33.000000000 +0000 @@ -39,6 +39,7 @@ from ansible.module_utils.facts import Facts from ansible.playbook.helpers import load_list_of_blocks from ansible.playbook.included_file import IncludedFile +from ansible.playbook.task_include import TaskInclude from ansible.plugins import action_loader, connection_loader, filter_loader, lookup_loader, module_loader, test_loader from ansible.template import Templar from ansible.utils.unicode import to_unicode @@ -344,21 +345,48 @@ continue return None + def parent_handler_match(target_handler, handler_name): + if target_handler: + if isinstance(target_handler, TaskInclude): + try: + handler_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, task=target_handler) + templar = Templar(loader=self._loader, variables=handler_vars) + target_handler_name = templar.template(target_handler.name) + if target_handler_name == handler_name: + return True + else: + target_handler_name = templar.template(target_handler.get_name()) + if target_handler_name == handler_name: + return True + except (UndefinedError, AnsibleUndefinedVariable) as e: + pass + return parent_handler_match(target_handler._task_include, handler_name) + else: + return False + # Find the handler using the above helper. First we look up the # dependency chain of the current task (if it's from a role), otherwise # we just look through the list of handlers in the current play/all # roles and use the first one that matches the notify name target_handler = search_handler_blocks(iterator._play.handlers) - if target_handler is None: - raise AnsibleError("The requested handler '%s' was not found in any of the known handlers" % handler_name) - - if target_handler in self._notified_handlers: + if target_handler is not None: if original_host not in self._notified_handlers[target_handler]: self._notified_handlers[target_handler].append(original_host) # FIXME: should this be a callback? display.vv("NOTIFIED HANDLER %s" % (handler_name,)) else: - raise AnsibleError("The requested handler '%s' was not found in the main handlers list" % handler_name) + # As there may be more than one handler with the notified name as the + # parent, so we just keep track of whether or not we found one at all + found = False + for target_handler in self._notified_handlers: + if parent_handler_match(target_handler, handler_name): + self._notified_handlers[target_handler].append(original_host) + display.vv("NOTIFIED HANDLER %s" % (target_handler.get_name(),)) + found = True + + # and if none were found, then we raise an error + if not found: + raise AnsibleError("The requested handler '%s' was not found in the main handlers list" % handler_name) elif result[0] == 'register_host_var': # essentially the same as 'set_host_var' below, however we @@ -547,11 +575,28 @@ elif not isinstance(data, list): raise AnsibleError("included task files must contain a list of tasks") + ti_copy = included_file._task.copy() + temp_vars = ti_copy.vars.copy() + temp_vars.update(included_file._args) + # pop tags out of the include args, if they were specified there, and assign + # them to the include. If the include already had tags specified, we raise an + # error so that users know not to specify them both ways + tags = included_file._task.vars.pop('tags', []) + if isinstance(tags, string_types): + tags = tags.split(',') + if len(tags) > 0: + if len(included_file._task.tags) > 0: + raise AnsibleParserError("Include tasks should not specify tags in more than one way (both via args and directly on the task). Mixing tag specify styles is prohibited for whole import hierarchy, not only for single import statement", + obj=included_file._task._ds) + display.deprecated("You should not specify tags in the include parameters. All tags should be specified using the task-level option") + included_file._task.tags = tags + ti_copy.vars = temp_vars + block_list = load_list_of_blocks( data, play=included_file._task._block._play, parent_block=None, - task_include=included_file._task, + task_include=ti_copy, role=included_file._task._role, use_handlers=is_handler, loader=self._loader, @@ -574,27 +619,6 @@ self._tqm.send_callback('v2_runner_on_failed', tr) return [] - # set the vars for this task from those specified as params to the include - for b in block_list: - # first make a copy of the including task, so that each has a unique copy to modify - b._task_include = b._task_include.copy() - # then we create a temporary set of vars to ensure the variable reference is unique - temp_vars = b._task_include.vars.copy() - temp_vars.update(included_file._args.copy()) - # pop tags out of the include args, if they were specified there, and assign - # them to the include. If the include already had tags specified, we raise an - # error so that users know not to specify them both ways - tags = temp_vars.pop('tags', []) - if isinstance(tags, string_types): - tags = tags.split(',') - if len(tags) > 0: - if len(b._task_include.tags) > 0: - raise AnsibleParserError("Include tasks should not specify tags in more than one way (both via args and directly on the task). Mixing tag specify styles is prohibited for whole import hierarchy, not only for single import statement", - obj=included_file._task._ds) - display.deprecated("You should not specify tags in the include parameters. All tags should be specified using the task-level option") - b._task_include.tags = tags - b._task_include.vars = temp_vars - # finally, send the callback and return the list of blocks loaded self._tqm.send_callback('v2_playbook_on_include', included_file) display.debug("done processing included file") diff -Nru ansible-2.1.1.0/lib/ansible/plugins/strategy/linear.py ansible-2.1.2.0/lib/ansible/plugins/strategy/linear.py --- ansible-2.1.1.0/lib/ansible/plugins/strategy/linear.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/plugins/strategy/linear.py 2016-09-29 16:06:33.000000000 +0000 @@ -19,7 +19,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible.compat.six import iteritems, text_type +from ansible.compat.six import iteritems from ansible.errors import AnsibleError from ansible.executor.play_iterator import PlayIterator @@ -69,9 +69,12 @@ if state_task and state_task[1]] if host_tasks_to_run: - lowest_cur_block = min( - (s.cur_block for h, (s, t) in host_tasks_to_run - if s.run_state != PlayIterator.ITERATING_COMPLETE)) + try: + lowest_cur_block = min( + (s.cur_block for h, (s, t) in host_tasks_to_run + if s.run_state != PlayIterator.ITERATING_COMPLETE)) + except ValueError: + lowest_cur_block = None else: # empty host_tasks_to_run will just run till the end of the function # without ever touching lowest_cur_block @@ -238,7 +241,7 @@ saved_name = task.name display.debug("done copying, going to template now") try: - task.name = text_type(templar.template(task.name, fail_on_undefined=False)) + task.name = to_unicode(templar.template(task.name, fail_on_undefined=False), nonstring='empty') display.debug("done templating") except: # just ignore any errors during task name templating, diff -Nru ansible-2.1.1.0/lib/ansible/release.py ansible-2.1.2.0/lib/ansible/release.py --- ansible-2.1.1.0/lib/ansible/release.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/release.py 2016-09-29 16:06:33.000000000 +0000 @@ -19,5 +19,5 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -__version__ = '2.1.1.0' +__version__ = '2.1.2.0' __author__ = 'Ansible, Inc.' diff -Nru ansible-2.1.1.0/lib/ansible/utils/path.py ansible-2.1.2.0/lib/ansible/utils/path.py --- ansible-2.1.1.0/lib/ansible/utils/path.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/utils/path.py 2016-09-29 16:06:33.000000000 +0000 @@ -30,7 +30,7 @@ example: '$HOME/../../var/mail' becomes '/var/spool/mail' ''' - return os.path.normpath(os.path.realpath(os.path.expandvars(os.path.expanduser(path)))) + return os.path.normpath(os.path.realpath(os.path.expanduser(os.path.expandvars(path)))) def makedirs_safe(path, mode=None): '''Safe way to create dirs in muliprocess/thread environments''' diff -Nru ansible-2.1.1.0/lib/ansible/vars/hostvars.py ansible-2.1.2.0/lib/ansible/vars/hostvars.py --- ansible-2.1.1.0/lib/ansible/vars/hostvars.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/vars/hostvars.py 2016-09-29 16:06:33.000000000 +0000 @@ -62,19 +62,25 @@ self._inventory = inventory def _find_host(self, host_name): - if host_name in C.LOCALHOST: + if host_name in C.LOCALHOST and self._inventory.localhost: host = self._inventory.localhost else: host = self._inventory.get_host(host_name) return host - def __getitem__(self, host_name): + def raw_get(self, host_name): + ''' + Similar to __getitem__, however the returned data is not run through + the templating engine to expand variables in the hostvars. + ''' host = self._find_host(host_name) if host is None: raise j2undefined - data = self._variable_manager.get_vars(loader=self._loader, host=host, include_hostvars=False) + return self._variable_manager.get_vars(loader=self._loader, host=host, include_hostvars=False) + def __getitem__(self, host_name): + data = self.raw_get(host_name) sha1_hash = sha1(str(data).encode('utf-8')).hexdigest() if sha1_hash in self._cached_result: result = self._cached_result[sha1_hash] diff -Nru ansible-2.1.1.0/lib/ansible/vars/__init__.py ansible-2.1.2.0/lib/ansible/vars/__init__.py --- ansible-2.1.1.0/lib/ansible/vars/__init__.py 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible/vars/__init__.py 2016-09-29 16:06:33.000000000 +0000 @@ -21,8 +21,7 @@ import os -from collections import defaultdict -from collections import MutableMapping +from collections import defaultdict, MutableMapping from ansible.compat.six import iteritems from jinja2.exceptions import UndefinedError @@ -237,7 +236,10 @@ # sure it sees its defaults above any other roles, as we previously # (v1) made sure each task had a copy of its roles default vars if task and task._role is not None: - all_vars = combine_vars(all_vars, task._role.get_default_vars(dep_chain=task._block.get_dep_chain())) + dep_chain = [] + if task._block: + dep_chain = task._block.get_dep_chain() + all_vars = combine_vars(all_vars, task._role.get_default_vars(dep_chain=dep_chain)) if host: # next, if a host is specified, we load any vars from group_vars @@ -334,7 +336,10 @@ # vars (which will look at parent blocks/task includes) if task: if task._role: - all_vars = combine_vars(all_vars, task._role.get_vars(task._block._dep_chain, include_params=False)) + dep_chain = [] + if task._block: + dep_chain = task._block.get_dep_chain() + all_vars = combine_vars(all_vars, task._role.get_vars(dep_chain=dep_chain, include_params=False)) all_vars = combine_vars(all_vars, task.get_vars()) # next, we merge in the vars cache (include vars) and nonpersistent @@ -346,7 +351,10 @@ # next, we merge in role params and task include params if task: if task._role: - all_vars = combine_vars(all_vars, task._role.get_role_params(task._block.get_dep_chain())) + dep_chain = [] + if task._block: + dep_chain = task._block.get_dep_chain() + all_vars = combine_vars(all_vars, task._role.get_role_params(dep_chain=dep_chain)) # special case for include tasks, where the include params # may be specified in the vars field for the task, which should @@ -619,6 +627,20 @@ return data + def clear_playbook_hostgroup_vars_files(self, path): + for f in self._host_vars_files.keys(): + keepers = [] + for entry in self._host_vars_files[f]: + if os.path.dirname(entry.path) != os.path.join(path, 'host_vars'): + keepers.append(entry) + self._host_vars_files[f] = keepers + for f in self._group_vars_files.keys(): + keepers = [] + for entry in self._group_vars_files[f]: + if os.path.dirname(entry.path) != os.path.join(path, 'group_vars'): + keepers.append(entry) + self._group_vars_files[f] = keepers + def clear_facts(self, hostname): ''' Clears the facts for a host diff -Nru ansible-2.1.1.0/lib/ansible.egg-info/PKG-INFO ansible-2.1.2.0/lib/ansible.egg-info/PKG-INFO --- ansible-2.1.1.0/lib/ansible.egg-info/PKG-INFO 2016-07-28 17:54:49.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible.egg-info/PKG-INFO 2016-09-29 16:06:45.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: ansible -Version: 2.1.1.0 +Version: 2.1.2.0 Summary: Radically simple IT automation Home-page: http://ansible.com/ Author: Ansible, Inc. diff -Nru ansible-2.1.1.0/lib/ansible.egg-info/SOURCES.txt ansible-2.1.2.0/lib/ansible.egg-info/SOURCES.txt --- ansible-2.1.1.0/lib/ansible.egg-info/SOURCES.txt 2016-07-28 17:54:49.000000000 +0000 +++ ansible-2.1.2.0/lib/ansible.egg-info/SOURCES.txt 2016-09-29 16:06:45.000000000 +0000 @@ -186,13 +186,12 @@ lib/ansible/module_utils/vca.py lib/ansible/module_utils/vmware.py lib/ansible/modules/__init__.py -lib/ansible/modules/core/.travis.yml lib/ansible/modules/core/CONTRIBUTING.md lib/ansible/modules/core/COPYING lib/ansible/modules/core/README.md lib/ansible/modules/core/VERSION lib/ansible/modules/core/__init__.py -lib/ansible/modules/core/test-docs.sh +lib/ansible/modules/core/shippable.yml lib/ansible/modules/core/test-requirements.txt lib/ansible/modules/core/.github/ISSUE_TEMPLATE.md lib/ansible/modules/core/.github/PULL_REQUEST_TEMPLATE.md @@ -445,6 +444,10 @@ lib/ansible/modules/core/system/sysctl.py lib/ansible/modules/core/system/user.py lib/ansible/modules/core/test/unit/cloud/openstack/test_os_server.py +lib/ansible/modules/core/test/utils/shippable/ci.sh +lib/ansible/modules/core/test/utils/shippable/sanity-skip-python24.txt +lib/ansible/modules/core/test/utils/shippable/sanity-test-python24.txt +lib/ansible/modules/core/test/utils/shippable/sanity.sh lib/ansible/modules/core/utilities/__init__.py lib/ansible/modules/core/utilities/helper/__init__.py lib/ansible/modules/core/utilities/helper/_fireball.py @@ -491,14 +494,13 @@ lib/ansible/modules/core/windows/win_template.py lib/ansible/modules/core/windows/win_user.ps1 lib/ansible/modules/core/windows/win_user.py -lib/ansible/modules/extras/.travis.yml lib/ansible/modules/extras/CONTRIBUTING.md lib/ansible/modules/extras/COPYING lib/ansible/modules/extras/README.md lib/ansible/modules/extras/REVIEWERS.md lib/ansible/modules/extras/VERSION lib/ansible/modules/extras/__init__.py -lib/ansible/modules/extras/test-docs.sh +lib/ansible/modules/extras/shippable.yml lib/ansible/modules/extras/.github/ISSUE_TEMPLATE.md lib/ansible/modules/extras/.github/PULL_REQUEST_TEMPLATE.md lib/ansible/modules/extras/cloud/__init__.py @@ -832,6 +834,10 @@ lib/ansible/modules/extras/system/svc.py lib/ansible/modules/extras/system/ufw.py lib/ansible/modules/extras/system/zfs.py +lib/ansible/modules/extras/test/utils/shippable/ci.sh +lib/ansible/modules/extras/test/utils/shippable/sanity-skip-python24.txt +lib/ansible/modules/extras/test/utils/shippable/sanity-test-python24.txt +lib/ansible/modules/extras/test/utils/shippable/sanity.sh lib/ansible/modules/extras/web_infrastructure/__init__.py lib/ansible/modules/extras/web_infrastructure/deploy_helper.py lib/ansible/modules/extras/web_infrastructure/ejabberd_user.py diff -Nru ansible-2.1.1.0/Makefile ansible-2.1.2.0/Makefile --- ansible-2.1.1.0/Makefile 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/Makefile 2016-09-29 16:06:33.000000000 +0000 @@ -67,7 +67,7 @@ DEBUILD_OPTS += -k$(DEBSIGN_KEYID) endif else - DEB_RELEASE = 0.git$(DATE)$(GITINFO) + DEB_RELEASE = 100.git$(DATE)$(GITINFO) # Do not sign unofficial builds DEBUILD_OPTS += -uc -us DPUT_OPTS += -u @@ -83,7 +83,7 @@ RPMDIST = $(shell rpm --eval '%{?dist}') RPMRELEASE = $(RELEASE) ifneq ($(OFFICIAL),yes) - RPMRELEASE = 0.git$(DATE)$(GITINFO) + RPMRELEASE = 100.git$(DATE)$(GITINFO) endif RPMNVR = "$(NAME)-$(VERSION)-$(RPMRELEASE)$(RPMDIST)" diff -Nru ansible-2.1.1.0/packaging/debian/changelog ansible-2.1.2.0/packaging/debian/changelog --- ansible-2.1.1.0/packaging/debian/changelog 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/packaging/debian/changelog 2016-09-29 16:06:33.000000000 +0000 @@ -4,6 +4,13 @@ -- Ansible, Inc. %DATE% +ansible (2.1.2.0) unstable; urgency=low + + * 2.1.2.0 + + -- Ansible, Inc. Thu, 29 Sep 2016 10:01:34 -0500 + + ansible (2.1.1.0) unstable; urgency=low * 2.1.1.0 diff -Nru ansible-2.1.1.0/packaging/rpm/ansible.spec ansible-2.1.2.0/packaging/rpm/ansible.spec --- ansible-2.1.1.0/packaging/rpm/ansible.spec 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/packaging/rpm/ansible.spec 2016-09-29 16:06:33.000000000 +0000 @@ -125,6 +125,9 @@ %changelog +* Thu Sep 29 2016 Ansible, Inc. - 2.1.2.0-1 +- Release 2.1.2.0-1 + * Thu Jul 28 2016 Ansible, Inc. - 2.1.1.0-1 - Release 2.1.1.0-1 diff -Nru ansible-2.1.1.0/PKG-INFO ansible-2.1.2.0/PKG-INFO --- ansible-2.1.1.0/PKG-INFO 2016-07-28 17:54:50.000000000 +0000 +++ ansible-2.1.2.0/PKG-INFO 2016-09-29 16:06:46.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: ansible -Version: 2.1.1.0 +Version: 2.1.2.0 Summary: Radically simple IT automation Home-page: http://ansible.com/ Author: Ansible, Inc. diff -Nru ansible-2.1.1.0/README.md ansible-2.1.2.0/README.md --- ansible-2.1.1.0/README.md 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/README.md 2016-09-29 16:06:33.000000000 +0000 @@ -1,7 +1,6 @@ [![PyPI version](https://img.shields.io/pypi/v/ansible.svg)](https://pypi.python.org/pypi/ansible) [![PyPI downloads](https://img.shields.io/pypi/dm/ansible.svg)](https://pypi.python.org/pypi/ansible) -[![Build Status](https://travis-ci.org/ansible/ansible.svg?branch=devel)](https://travis-ci.org/ansible/ansible) - +[![Build Status](https://api.shippable.com/projects/573f79d02a8192902e20e34b/badge?branch=stable-2.1)](https://app.shippable.com/projects/573f79d02a8192902e20e34b) Ansible ======= diff -Nru ansible-2.1.1.0/VERSION ansible-2.1.2.0/VERSION --- ansible-2.1.1.0/VERSION 2016-07-28 17:54:39.000000000 +0000 +++ ansible-2.1.2.0/VERSION 2016-09-29 16:06:33.000000000 +0000 @@ -1 +1 @@ -2.1.1.0 1 +2.1.2.0 1