diff -Nru ansible-2.5.3/changelogs/CHANGELOG-v2.5.rst ansible-2.5.4/changelogs/CHANGELOG-v2.5.rst --- ansible-2.5.3/changelogs/CHANGELOG-v2.5.rst 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/changelogs/CHANGELOG-v2.5.rst 2018-05-31 22:01:21.000000000 +0000 @@ -2,6 +2,64 @@ Ansible 2.5 "Kashmir" Release Notes =================================== +.. _Ansible 2.5 "Kashmir" Release Notes_v2.5.4: + +v2.5.4 +====== + +.. _Ansible 2.5 "Kashmir" Release Notes_v2.5.4_Release Summary: + +Release Summary +--------------- + +| Release Date: 2018-05-31 +| `Porting Guide `_ + + +.. _Ansible 2.5 "Kashmir" Release Notes_v2.5.4_Bugfixes: + +Bugfixes +-------- + +- jenkins_plugin - fix plugin always updated even if already uptodate (https://github.com/ansible/ansible/pull/40645) + +- ec2_asg - wait for lifecycle hooks to complete (https://github.com/ansible/ansible/issues/37281) + +- edgeos_config - check for a corresponding set command when issuing delete commands to ensure the desired state is met (https://github.com/ansible/ansible/issues/40437) + +- iptables - use suboptions to properly join tcp_flags options (https://github.com/ansible/ansible/issues/36490) + +- known_hosts - add better checking and error reporting to the host field (https://github.com/ansible/ansible/pull/38307) + +- Fix legacy Nexus 3k integration test and module issues (https://github.com/ansible/ansible/pull/40322). + +- Skip N35 and N3L platforms for nxos_evpn_global test (https://github.com/ansible/ansible/pull/40333). + +- Add normalize_interface in module_utils and fix nxos_l3_interface module (https://github.com/ansible/ansible/pull/40598). + +- Fix nxos_interface Disable switchport for loopback/svi (https://github.com/ansible/ansible/pull/40314). + +- fixes bug with matching nxos prompts (https://github.com/ansible/ansible/pull/40655). + +- fix nxos_vrf and migrate get_interface_type to module_utils (https://github.com/ansible/ansible/pull/40825). + +- Fix nxos_vlan vlan creation failure (https://github.com/ansible/ansible/pull/40822). + +- pause - ensure ctrl+c interrupt works in all cases (https://github.com/ansible/ansible/issues/35372) + +- user - With python 3.6 spwd.getspnam returns PermissionError instead of KeyError if user does not have privileges (https://github.com/ansible/ansible/issues/39472) + +- synchronize - Ensure the local connection created by synchronize uses _remote_is_local=True, which causes ActionBase to build a local tmpdir (https://github.com/ansible/ansible/pull/40833) + +- win_get_url - fixed issue when authenticating when force=yes https://github.com/ansible/ansible/pull/40641 + +- winrm - allow `ansible_user` or `ansible_winrm_user` to override `ansible_ssh_user` when both are defined in an inventory - https://github.com/ansible/ansible/issues/39844 + +- winrm - Add better error handling when the kinit process fails + +- xenserver_facts - ensure module works with newer versions of XenServer (https://github.com/ansible/ansible/pull/35821) + + .. _Ansible 2.5 "Kashmir" Release Notes_v2.5.3: v2.5.3 @@ -391,7 +449,7 @@ - win_copy - Preserve the local tmp folder instead of deleting it so future tasks can use it (https://github.com/ansible/ansible/pull/37964) -- powershell - fixed issue with passing in a bool and int to the Windows environment block, also allow special chars in the env key name (https://github.com/ansible/ansible/pull/37215) +- win_environment - Fix for issue where the environment value was deleted when a null value or empty string was set - https://github.com/ansible/ansible/issues/40450 - Ansible.ModuleUtils.FileUtil - Catch DirectoryNotFoundException with Test-AnsiblePath (https://github.com/ansible/ansible/pull/37968) diff -Nru ansible-2.5.3/debian/changelog ansible-2.5.4/debian/changelog --- ansible-2.5.3/debian/changelog 2018-05-17 23:52:40.000000000 +0000 +++ ansible-2.5.4/debian/changelog 2018-05-31 22:02:14.000000000 +0000 @@ -1,8 +1,8 @@ -ansible (2.5.3-1ppa~bionic) bionic; urgency=low +ansible (2.5.4-1ppa~bionic) bionic; urgency=low - * 2.5.3 release + * 2.5.4 release - -- Ansible, Inc. Thu, 17 May 2018 23:52:08 +0000 + -- Ansible, Inc. Thu, 31 May 2018 22:01:53 +0000 ansible (2.5.2-1) unstable; urgency=low diff -Nru ansible-2.5.3/docs/docsite/rst/network/user_guide/platform_eos.rst ansible-2.5.4/docs/docsite/rst/network/user_guide/platform_eos.rst --- ansible-2.5.3/docs/docsite/rst/network/user_guide/platform_eos.rst 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/docs/docsite/rst/network/user_guide/platform_eos.rst 2018-05-31 22:01:21.000000000 +0000 @@ -130,4 +130,4 @@ - the ``proxy_env`` variable gets passed to the ``environment`` option of the module -.. include:: shared_snippets/SSH_warning.rst +.. include:: shared_snippets/SSH_warning.txt diff -Nru ansible-2.5.3/docs/docsite/rst/network/user_guide/platform_index.rst ansible-2.5.4/docs/docsite/rst/network/user_guide/platform_index.rst --- ansible-2.5.3/docs/docsite/rst/network/user_guide/platform_index.rst 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/docs/docsite/rst/network/user_guide/platform_index.rst 2018-05-31 22:01:21.000000000 +0000 @@ -14,3 +14,36 @@ platform_ios platform_junos platform_nxos + +.. _settings_by_platform: + +Settings by Platform +================================ + ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ +|.. | | ``ansible_connection:`` settings available | ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ +| Network OS | ``ansible_network_os:`` | network_cli | netconf | httpapi | local | ++================+=========================+======================+======================+==================+==================+ +| Arista EOS* | ``eos`` | in v. >=2.5 | N/A | in v. >=2.6 | in v. >=2.4 | ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ +| Cisco ASA | ``asa`` | in v. >=2.5 | N/A | N/A | in v. >=2.4 | ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ +| Cisco IOS* | ``ios`` | in v. >=2.5 | N/A | N/A | in v. >=2.4 | ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ +| Cisco IOS XR* | ``iosxr`` | in v. >=2.5 | N/A | N/A | in v. >=2.4 | ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ +| Cisco NX-OS* | ``nxos`` | in v. >=2.5 | N/A | in v. >=2.6 | in v. >=2.4 | ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ +| F5 BIG-IP | N/A | N/A | N/A | N/A | in v. >=2.0 | ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ +| F5 BIG-IQ | N/A | N/A | N/A | N/A | in v. >=2.0 | ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ +| Junos OS* | ``junos`` | in v. >=2.5 | in v. >=2.5 | N/A | in v. >=2.4 | ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ +| Nokia SR OS | ``sros`` | in v. >=2.5 | N/A | N/A | in v. >=2.4 | ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ +| VyOS* | ``vyos`` | in v. >=2.5 | N/A | N/A | in v. >=2.4 | ++----------------+-------------------------+----------------------+----------------------+------------------+------------------+ + +`*` Maintained by Ansible Network Team \ No newline at end of file diff -Nru ansible-2.5.3/docs/docsite/rst/network/user_guide/platform_ios.rst ansible-2.5.4/docs/docsite/rst/network/user_guide/platform_ios.rst --- ansible-2.5.3/docs/docsite/rst/network/user_guide/platform_ios.rst 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/docs/docsite/rst/network/user_guide/platform_ios.rst 2018-05-31 22:01:21.000000000 +0000 @@ -67,4 +67,4 @@ register: backup_ios_location when: ansible_network_os == 'ios' -.. include:: shared_snippets/SSH_warning.rst +.. include:: shared_snippets/SSH_warning.txt diff -Nru ansible-2.5.3/docs/docsite/rst/network/user_guide/platform_junos.rst ansible-2.5.4/docs/docsite/rst/network/user_guide/platform_junos.rst --- ansible-2.5.3/docs/docsite/rst/network/user_guide/platform_junos.rst 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/docs/docsite/rst/network/user_guide/platform_junos.rst 2018-05-31 22:01:21.000000000 +0000 @@ -112,4 +112,4 @@ when: ansible_network_os == 'junos' -.. include:: shared_snippets/SSH_warning.rst +.. include:: shared_snippets/SSH_warning.txt diff -Nru ansible-2.5.3/docs/docsite/rst/network/user_guide/platform_nxos.rst ansible-2.5.4/docs/docsite/rst/network/user_guide/platform_nxos.rst --- ansible-2.5.3/docs/docsite/rst/network/user_guide/platform_nxos.rst 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/docs/docsite/rst/network/user_guide/platform_nxos.rst 2018-05-31 22:01:21.000000000 +0000 @@ -126,4 +126,4 @@ - the ``proxy_env`` variable gets passed to the ``environment`` option of the module -.. include:: shared_snippets/SSH_warning.rst +.. include:: shared_snippets/SSH_warning.txt diff -Nru ansible-2.5.3/docs/docsite/rst/network/user_guide/shared_snippets/SSH_warning.rst ansible-2.5.4/docs/docsite/rst/network/user_guide/shared_snippets/SSH_warning.rst --- ansible-2.5.3/docs/docsite/rst/network/user_guide/shared_snippets/SSH_warning.rst 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/docs/docsite/rst/network/user_guide/shared_snippets/SSH_warning.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -:orphan: - -.. warning:: - Never store passwords in plain text. We recommend using SSH keys to authenticate SSH connections. Ansible supports ssh-agent to manage your SSH keys. If you must use passwords to authenticate SSH connections, we recommend encrypting them with :ref:`Ansible Vault `. diff -Nru ansible-2.5.3/docs/docsite/rst/network/user_guide/shared_snippets/SSH_warning.txt ansible-2.5.4/docs/docsite/rst/network/user_guide/shared_snippets/SSH_warning.txt --- ansible-2.5.3/docs/docsite/rst/network/user_guide/shared_snippets/SSH_warning.txt 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/docs/docsite/rst/network/user_guide/shared_snippets/SSH_warning.txt 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1,2 @@ +.. warning:: + Never store passwords in plain text. We recommend using SSH keys to authenticate SSH connections. Ansible supports ssh-agent to manage your SSH keys. If you must use passwords to authenticate SSH connections, we recommend encrypting them with :ref:`Ansible Vault `. diff -Nru ansible-2.5.3/docs/docsite/rst/porting_guides/porting_guide_2.5.rst ansible-2.5.4/docs/docsite/rst/porting_guides/porting_guide_2.5.rst --- ansible-2.5.3/docs/docsite/rst/porting_guides/porting_guide_2.5.rst 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/docs/docsite/rst/porting_guides/porting_guide_2.5.rst 2018-05-31 22:01:21.000000000 +0000 @@ -75,6 +75,22 @@ The relevant change in those examples is, that in Ansible 2.5, the included file defines the tag ``distro_include`` again. The tag is not inherited automatically. +Fixed handling of keywords and inline variables +----------------------------------------------- + +We made several fixes to how we handle keywords and 'inline variables', to avoid conflating the two. Unfortunately these changes mean you must specify whether `name` is a keyword or a variable when calling roles. If you have playbooks that look like this:: + + roles: + - { role: myrole, name: Justin, othervar: othervalue, become: True} + +You will run into errors because Ansible reads name in this context as a keyword. Beginning in 2.5, if you want to use a variable name that is also a keyword, you must explicitly declare it as a variable for the role:: + + roles: + - { role: myrole, vars: {name: Justin, othervar: othervalue}, become: True} + + +For a full list of keywords see ::ref::`Playbook Keywords`. + Deprecated ========== diff -Nru ansible-2.5.3/docs/man/man1/ansible.1 ansible-2.5.4/docs/man/man1/ansible.1 --- ansible-2.5.3/docs/man/man1/ansible.1 2018-05-17 23:52:20.000000000 +0000 +++ ansible-2.5.4/docs/man/man1/ansible.1 2018-05-31 22:02:01.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 05/17/2018 +.\" Date: 05/31/2018 .\" Manual: System administration commands -.\" Source: Ansible 2.5.3 +.\" Source: Ansible 2.5.4 .\" Language: English .\" -.TH "ANSIBLE" "1" "05/17/2018" "Ansible 2\&.5\&.3" "System administration commands" +.TH "ANSIBLE" "1" "05/31/2018" "Ansible 2\&.5\&.4" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.5.3/docs/man/man1/ansible-config.1 ansible-2.5.4/docs/man/man1/ansible-config.1 --- ansible-2.5.3/docs/man/man1/ansible-config.1 2018-05-17 23:52:13.000000000 +0000 +++ ansible-2.5.4/docs/man/man1/ansible-config.1 2018-05-31 22:01:55.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-config .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 05/17/2018 +.\" Date: 05/31/2018 .\" Manual: System administration commands -.\" Source: Ansible 2.5.3 +.\" Source: Ansible 2.5.4 .\" Language: English .\" -.TH "ANSIBLE\-CONFIG" "1" "05/17/2018" "Ansible 2\&.5\&.3" "System administration commands" +.TH "ANSIBLE\-CONFIG" "1" "05/31/2018" "Ansible 2\&.5\&.4" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.5.3/docs/man/man1/ansible-console.1 ansible-2.5.4/docs/man/man1/ansible-console.1 --- ansible-2.5.3/docs/man/man1/ansible-console.1 2018-05-17 23:52:14.000000000 +0000 +++ ansible-2.5.4/docs/man/man1/ansible-console.1 2018-05-31 22:01:56.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-console .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 05/17/2018 +.\" Date: 05/31/2018 .\" Manual: System administration commands -.\" Source: Ansible 2.5.3 +.\" Source: Ansible 2.5.4 .\" Language: English .\" -.TH "ANSIBLE\-CONSOLE" "1" "05/17/2018" "Ansible 2\&.5\&.3" "System administration commands" +.TH "ANSIBLE\-CONSOLE" "1" "05/31/2018" "Ansible 2\&.5\&.4" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.5.3/docs/man/man1/ansible-doc.1 ansible-2.5.4/docs/man/man1/ansible-doc.1 --- ansible-2.5.3/docs/man/man1/ansible-doc.1 2018-05-17 23:52:15.000000000 +0000 +++ ansible-2.5.4/docs/man/man1/ansible-doc.1 2018-05-31 22:01:56.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-doc .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 05/17/2018 +.\" Date: 05/31/2018 .\" Manual: System administration commands -.\" Source: Ansible 2.5.3 +.\" Source: Ansible 2.5.4 .\" Language: English .\" -.TH "ANSIBLE\-DOC" "1" "05/17/2018" "Ansible 2\&.5\&.3" "System administration commands" +.TH "ANSIBLE\-DOC" "1" "05/31/2018" "Ansible 2\&.5\&.4" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.5.3/docs/man/man1/ansible-galaxy.1 ansible-2.5.4/docs/man/man1/ansible-galaxy.1 --- ansible-2.5.3/docs/man/man1/ansible-galaxy.1 2018-05-17 23:52:16.000000000 +0000 +++ ansible-2.5.4/docs/man/man1/ansible-galaxy.1 2018-05-31 22:01:57.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-galaxy .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 05/17/2018 +.\" Date: 05/31/2018 .\" Manual: System administration commands -.\" Source: Ansible 2.5.3 +.\" Source: Ansible 2.5.4 .\" Language: English .\" -.TH "ANSIBLE\-GALAXY" "1" "05/17/2018" "Ansible 2\&.5\&.3" "System administration commands" +.TH "ANSIBLE\-GALAXY" "1" "05/31/2018" "Ansible 2\&.5\&.4" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.5.3/docs/man/man1/ansible-inventory.1 ansible-2.5.4/docs/man/man1/ansible-inventory.1 --- ansible-2.5.3/docs/man/man1/ansible-inventory.1 2018-05-17 23:52:16.000000000 +0000 +++ ansible-2.5.4/docs/man/man1/ansible-inventory.1 2018-05-31 22:01:58.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-inventory .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 05/17/2018 +.\" Date: 05/31/2018 .\" Manual: System administration commands -.\" Source: Ansible 2.5.3 +.\" Source: Ansible 2.5.4 .\" Language: English .\" -.TH "ANSIBLE\-INVENTORY" "1" "05/17/2018" "Ansible 2\&.5\&.3" "System administration commands" +.TH "ANSIBLE\-INVENTORY" "1" "05/31/2018" "Ansible 2\&.5\&.4" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.5.3/docs/man/man1/ansible-playbook.1 ansible-2.5.4/docs/man/man1/ansible-playbook.1 --- ansible-2.5.3/docs/man/man1/ansible-playbook.1 2018-05-17 23:52:17.000000000 +0000 +++ ansible-2.5.4/docs/man/man1/ansible-playbook.1 2018-05-31 22:01:59.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-playbook .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 05/17/2018 +.\" Date: 05/31/2018 .\" Manual: System administration commands -.\" Source: Ansible 2.5.3 +.\" Source: Ansible 2.5.4 .\" Language: English .\" -.TH "ANSIBLE\-PLAYBOOK" "1" "05/17/2018" "Ansible 2\&.5\&.3" "System administration commands" +.TH "ANSIBLE\-PLAYBOOK" "1" "05/31/2018" "Ansible 2\&.5\&.4" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.5.3/docs/man/man1/ansible-pull.1 ansible-2.5.4/docs/man/man1/ansible-pull.1 --- ansible-2.5.3/docs/man/man1/ansible-pull.1 2018-05-17 23:52:18.000000000 +0000 +++ ansible-2.5.4/docs/man/man1/ansible-pull.1 2018-05-31 22:01:59.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-pull .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 05/17/2018 +.\" Date: 05/31/2018 .\" Manual: System administration commands -.\" Source: Ansible 2.5.3 +.\" Source: Ansible 2.5.4 .\" Language: English .\" -.TH "ANSIBLE\-PULL" "1" "05/17/2018" "Ansible 2\&.5\&.3" "System administration commands" +.TH "ANSIBLE\-PULL" "1" "05/31/2018" "Ansible 2\&.5\&.4" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.5.3/docs/man/man1/ansible-vault.1 ansible-2.5.4/docs/man/man1/ansible-vault.1 --- ansible-2.5.3/docs/man/man1/ansible-vault.1 2018-05-17 23:52:19.000000000 +0000 +++ ansible-2.5.4/docs/man/man1/ansible-vault.1 2018-05-31 22:02:00.000000000 +0000 @@ -2,12 +2,12 @@ .\" Title: ansible-vault .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 05/17/2018 +.\" Date: 05/31/2018 .\" Manual: System administration commands -.\" Source: Ansible 2.5.3 +.\" Source: Ansible 2.5.4 .\" Language: English .\" -.TH "ANSIBLE\-VAULT" "1" "05/17/2018" "Ansible 2\&.5\&.3" "System administration commands" +.TH "ANSIBLE\-VAULT" "1" "05/31/2018" "Ansible 2\&.5\&.4" "System administration commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -Nru ansible-2.5.3/lib/ansible/cli/doc.py ansible-2.5.4/lib/ansible/cli/doc.py --- ansible-2.5.3/lib/ansible/cli/doc.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/cli/doc.py 2018-05-31 22:01:21.000000000 +0000 @@ -22,6 +22,8 @@ import traceback import yaml +from collections import Sequence + from ansible import constants as C from ansible.cli import CLI from ansible.errors import AnsibleError, AnsibleOptionsError @@ -160,7 +162,7 @@ try: doc, plainexamples, returndocs, metadata = get_docstring(filename, fragment_loader, verbose=(self.options.verbosity > 0)) - except: + except Exception: display.vvv(traceback.format_exc()) display.error("%s %s has a documentation error formatting or is missing documentation." % (plugin_type, plugin), wrap_text=False) continue @@ -256,7 +258,7 @@ doc = None try: doc, plainexamples, returndocs, metadata = get_docstring(filename, fragment_loader) - except: + except Exception: display.warning("%s has a documentation formatting error" % plugin) if not doc or not isinstance(doc, dict): @@ -395,7 +397,7 @@ self.add_fields(text, opt.pop('spec'), limit, opt_indent + opt_indent) conf = {} - for config in ('env', 'ini', 'yaml', 'vars'): + for config in ('env', 'ini', 'yaml', 'vars', 'keywords'): if config in opt and opt[config]: conf[config] = opt.pop(config) for ignore in self.IGNORE: @@ -411,7 +413,7 @@ continue if isinstance(opt[k], string_types): text.append('%s%s: %s' % (opt_indent, k, textwrap.fill(CLI.tty_ify(opt[k]), limit - (len(k) + 2), subsequent_indent=opt_indent))) - elif isinstance(opt[k], (list, tuple)): + elif isinstance(opt[k], (Sequence)) and all(isinstance(x, string_types) for x in opt[k]): text.append(CLI.tty_ify('%s%s: %s' % (opt_indent, k, ', '.join(opt[k])))) else: text.append(self._dump_yaml({k: opt[k]}, opt_indent)) @@ -476,7 +478,7 @@ support_block = self.get_support_block(doc) if support_block: text.extend(support_block) - except: + except Exception: pass # FIXME: not suported by plugins if doc.pop('action', False): @@ -532,7 +534,7 @@ if metadata_block: text.extend(metadata_block) text.append('') - except: + except Exception: pass # metadata is optional return "\n".join(text) diff -Nru ansible-2.5.3/lib/ansible/executor/task_queue_manager.py ansible-2.5.4/lib/ansible/executor/task_queue_manager.py --- ansible-2.5.3/lib/ansible/executor/task_queue_manager.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/executor/task_queue_manager.py 2018-05-31 22:01:21.000000000 +0000 @@ -210,7 +210,7 @@ except AttributeError: display.deprecated("%s callback, does not support setting 'options', it will work for now, " " but this will be required in the future and should be updated, " - " see the 2.4 porting guide for details." % self.callback_obj._load_name, version="2.9") + " see the 2.4 porting guide for details." % callback_obj._load_name, version="2.9") self._callback_plugins.append(callback_obj) self._callbacks_loaded = True diff -Nru ansible-2.5.3/lib/ansible/modules/cloud/amazon/aws_ses_identity.py ansible-2.5.4/lib/ansible/modules/cloud/amazon/aws_ses_identity.py --- ansible-2.5.3/lib/ansible/modules/cloud/amazon/aws_ses_identity.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/cloud/amazon/aws_ses_identity.py 2018-05-31 22:01:21.000000000 +0000 @@ -309,8 +309,15 @@ return notification_attributes[identity] -def update_notification_topic(connection, module, identity, identity_notifications, notification_type): +def desired_topic(module, notification_type): arg_dict = module.params.get(notification_type.lower() + '_notifications') + if arg_dict: + return arg_dict.get('topic', None) + else: + return None + + +def update_notification_topic(connection, module, identity, identity_notifications, notification_type): topic_key = notification_type + 'Topic' if identity_notifications is None: # If there is no configuration for notifications cannot be being sent to topics @@ -325,10 +332,7 @@ # included but best to be defensive current = None - if arg_dict is not None and 'topic' in arg_dict: - required = arg_dict['topic'] - else: - required = None + required = desired_topic(module, notification_type) if current != required: call_and_handle_errors( @@ -375,6 +379,11 @@ def update_feedback_forwarding(connection, module, identity, identity_notifications): + if module.params.get('feedback_forwarding') is False: + if not (desired_topic(module, 'Bounce') and desired_topic(module, 'Complaint')): + module.fail_json(msg="Invalid Parameter Value 'False' for 'feedback_forwarding'. AWS requires " + "feedback forwarding to be enabled unless bounces and complaints are handled by SNS topics") + if identity_notifications is None: # AWS requires feedback forwarding to be enabled unless bounces and complaints # are being handled by SNS topics. So in the absence of identity_notifications diff -Nru ansible-2.5.3/lib/ansible/modules/cloud/amazon/ec2_instance.py ansible-2.5.4/lib/ansible/modules/cloud/amazon/ec2_instance.py --- ansible-2.5.3/lib/ansible/modules/cloud/amazon/ec2_instance.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/cloud/amazon/ec2_instance.py 2018-05-31 22:01:21.000000000 +0000 @@ -784,7 +784,6 @@ if interface_params.get('ipv6_addresses'): spec['Ipv6Addresses'] = [{'Ipv6Address': a} for a in interface_params.get('ipv6_addresses', [])] - spec['Ipv6AddressCount'] = len(spec['Ipv6Addresses']) if interface_params.get('private_ip_address'): spec['PrivateIpAddress'] = interface_params.get('private_ip_address') diff -Nru ansible-2.5.3/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py ansible-2.5.4/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py --- ansible-2.5.3/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py 2018-05-31 22:01:21.000000000 +0000 @@ -823,7 +823,8 @@ if set(current_nics) != set(network_interfaces): self.log('CHANGED: virtual machine {0} - network interfaces are different.'.format(self.name)) differences.append('Network Interfaces') - updated_nics = [dict(id=id) for id in network_interfaces] + updated_nics = [dict(id=id, primary=(i is 0)) + for i, id in enumerate(network_interfaces)] vm_dict['properties']['networkProfile']['networkInterfaces'] = updated_nics changed = True @@ -932,7 +933,8 @@ if not self.short_hostname: self.short_hostname = self.name - nics = [self.compute_models.NetworkInterfaceReference(id=id) for id in network_interfaces] + nics = [self.compute_models.NetworkInterfaceReference(id=id, primary=(i is 0)) + for i, id in enumerate(network_interfaces)] # os disk if self.managed_disk_type: @@ -1061,9 +1063,8 @@ self.log("Update virtual machine {0}".format(self.name)) self.results['actions'].append('Updated VM {0}'.format(self.name)) - - nics = [self.compute_models.NetworkInterfaceReference(id=interface['id']) - for interface in vm_dict['properties']['networkProfile']['networkInterfaces']] + nics = [self.compute_models.NetworkInterfaceReference(id=interface['id'], primary=(i is 0)) + for i, interface in enumerate(vm_dict['properties']['networkProfile']['networkInterfaces'])] # os disk if not vm_dict['properties']['storageProfile']['osDisk'].get('managedDisk'): diff -Nru ansible-2.5.3/lib/ansible/modules/cloud/misc/xenserver_facts.py ansible-2.5.4/lib/ansible/modules/cloud/misc/xenserver_facts.py --- ansible-2.5.3/lib/ansible/modules/cloud/misc/xenserver_facts.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/cloud/misc/xenserver_facts.py 2018-05-31 22:01:21.000000000 +0000 @@ -22,12 +22,13 @@ author: - Andy Hill (@andyhky) - Tim Rupp + - Robin Lee (@cheese) options: {} ''' EXAMPLES = ''' - name: Gather facts from xenserver - xenserver: + xenserver_facts: - name: Print running VMs debug: @@ -91,11 +92,8 @@ def get_networks(session): recs = session.xenapi.network.get_all_records() - xs_networks = {} - networks = change_keys(recs, key='uuid') - for network in networks.values(): - xs_networks[network['name_label']] = network - return xs_networks + networks = change_keys(recs, key='name_label') + return networks def get_pifs(session): @@ -132,6 +130,13 @@ if filter_func is not None and not filter_func(rec): continue + for param_name, param_value in rec.items(): + # param_value may be of type xmlrpc.client.DateTime, + # which is not simply convertable to str. + # Use 'value' attr to get the str value, + # following an example in xmlrpc.client.DateTime document + if hasattr(param_value, "value"): + rec[param_name] = param_value.value new_recs[rec[key]] = rec new_recs[rec[key]]['ref'] = ref @@ -146,26 +151,19 @@ def get_vms(session): - xs_vms = {} - recs = session.xenapi.VM.get_all() + recs = session.xenapi.VM.get_all_records() if not recs: return None - - vms = change_keys(recs, key='uuid') - for vm in vms.values(): - xs_vms[vm['name_label']] = vm - return xs_vms + vms = change_keys(recs, key='name_label') + return vms def get_srs(session): - xs_srs = {} - recs = session.xenapi.SR.get_all() + recs = session.xenapi.SR.get_all_records() if not recs: return None - srs = change_keys(recs, key='uuid') - for sr in srs.values(): - xs_srs[sr['name_label']] = sr - return xs_srs + srs = change_keys(recs, key='name_label') + return srs def main(): @@ -204,7 +202,7 @@ if xs_srs: data['xs_srs'] = xs_srs - module.exit_json(ansible=data) + module.exit_json(ansible_facts=data) if __name__ == '__main__': diff -Nru ansible-2.5.3/lib/ansible/modules/files/synchronize.py ansible-2.5.4/lib/ansible/modules/files/synchronize.py --- ansible-2.5.3/lib/ansible/modules/files/synchronize.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/files/synchronize.py 2018-05-31 22:01:21.000000000 +0000 @@ -372,7 +372,7 @@ group=dict(type='bool'), set_remote_user=dict(type='bool', default=True), rsync_timeout=dict(type='int', default=0), - rsync_opts=dict(type='list'), + rsync_opts=dict(type='list', default=[]), ssh_args=dict(type='str'), partial=dict(type='bool', default=False), verify_host=dict(type='bool', default=False), diff -Nru ansible-2.5.3/lib/ansible/modules/network/edgeos/edgeos_command.py ansible-2.5.4/lib/ansible/modules/network/edgeos/edgeos_command.py --- ansible-2.5.3/lib/ansible/modules/network/edgeos/edgeos_command.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/edgeos/edgeos_command.py 2018-05-31 22:01:21.000000000 +0000 @@ -25,6 +25,9 @@ use a custom pager that can cause this module to hang. If the value of the environment variable C(ANSIBLE_EDGEOS_TERMINAL_LENGTH) is not set, the default number of 10000 is used. + - "This is a network module and requires C(connection: network_cli) + in order to work properly." + - For more information please see the L(Network Guide,../network/getting_started/index.html). options: commands: description: @@ -95,9 +98,10 @@ import time +from ansible.module_utils._text import to_text from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.common.utils import ComplexList from ansible.module_utils.network.common.parsing import Conditional +from ansible.module_utils.network.common.utils import ComplexList from ansible.module_utils.network.edgeos.edgeos import run_commands from ansible.module_utils.six import string_types @@ -105,7 +109,7 @@ def to_lines(stdout): for item in stdout: if isinstance(item, string_types): - item = str(item).split('\n') + item = to_text(item).split('\n') yield item @@ -149,7 +153,7 @@ try: conditionals = [Conditional(c) for c in wait_for] except AttributeError as e: - module.fail_json(msg=str(e)) + module.fail_json(msg=to_text(e)) retries = module.params['retries'] interval = module.params['interval'] diff -Nru ansible-2.5.3/lib/ansible/modules/network/edgeos/edgeos_config.py ansible-2.5.4/lib/ansible/modules/network/edgeos/edgeos_config.py --- ansible-2.5.3/lib/ansible/modules/network/edgeos/edgeos_config.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/edgeos/edgeos_config.py 2018-05-31 22:01:21.000000000 +0000 @@ -25,6 +25,9 @@ configuration file and state of the active configuration. All configuration statements are based on `set` and `delete` commands in the device configuration. + - "This is a network module and requires the C(connection: network_cli) in order + to work properly." + - For more information please see the L(Network Guide,../network/getting_started/index.html). notes: - Tested against EdgeOS 1.9.7 - Setting C(ANSIBLE_PERSISTENT_COMMAND_TIMEOUT) to 30 is recommended since @@ -59,13 +62,14 @@ choices: ['line', 'none'] backup: description: - - The C(backup) argument will backup the current devices active + - The C(backup) argument will backup the current device's active configuration to the Ansible control host prior to making any changes. The backup file will be located in the backup folder - in the root of the playbook - required: false - default: false - choices: ['yes', 'no'] + in the playbook root directory or role root directory if the + playbook is part of an ansible role. If the directory does not + exist, it is created. + type: bool + default: 'no' comment: description: - Allows a commit description to be specified to be included @@ -126,10 +130,12 @@ import re +from ansible.module_utils._text import to_native from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.network.common.config import NetworkConfig from ansible.module_utils.network.edgeos.edgeos import load_config, get_config, run_commands + DEFAULT_COMMENT = 'configured by edgeos_config' CONFIG_FILTERS = [ @@ -154,7 +160,7 @@ commands = ['set %s' % cmd.replace(' {', '') for cmd in commands] else: - commands = str(candidate).split('\n') + commands = to_native(candidate).split('\n') return commands @@ -169,19 +175,30 @@ def diff_config(commands, config): - config = [str(c).replace("'", '') for c in config.splitlines()] + config = [to_native(c).replace("'", '') for c in config.splitlines()] updates = list() visited = set() + delete_commands = [line for line in commands if line.startswith('delete')] for line in commands: - item = str(line).replace("'", '') + item = to_native(line).replace("'", '') if not item.startswith('set') and not item.startswith('delete'): raise ValueError('line must start with either `set` or `delete`') - elif item.startswith('set') and item not in config: - updates.append(line) + elif item.startswith('set'): + + if item not in config: + updates.append(line) + + # If there is a corresponding delete command in the desired config, make sure to append + # the set command even though it already exists in the running config + else: + ditem = re.sub('set', 'delete', item) + for line in delete_commands: + if ditem.startswith(line): + updates.append(item) elif item.startswith('delete'): if not config: diff -Nru ansible-2.5.3/lib/ansible/modules/network/ios/ios_l3_interface.py ansible-2.5.4/lib/ansible/modules/network/ios/ios_l3_interface.py --- ansible-2.5.3/lib/ansible/modules/network/ios/ios_l3_interface.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/ios/ios_l3_interface.py 2018-05-31 22:01:21.000000000 +0000 @@ -107,11 +107,10 @@ from ansible.module_utils._text import to_text from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.six import iteritems from ansible.module_utils.network.ios.ios import get_config, load_config from ansible.module_utils.network.ios.ios import ios_argument_spec from ansible.module_utils.network.common.config import NetworkConfig -from ansible.module_utils.network.common.utils import conditional, remove_default_spec +from ansible.module_utils.network.common.utils import remove_default_spec from ansible.module_utils.network.common.utils import is_netmask, is_masklen, to_netmask, to_masklen @@ -148,9 +147,18 @@ def parse_config_argument(configobj, name, arg=None): cfg = configobj['interface %s' % name] cfg = '\n'.join(cfg.children) - match = re.search(r'%s (.+)$' % arg, cfg, re.M) - if match: - return match.group(1).strip() + + values = [] + matches = re.finditer(r'%s (.+)$' % arg, cfg, re.M) + for match in matches: + match_str = match.group(1).strip() + if arg == 'ipv6 address': + values.append(match_str) + else: + values = match_str + break + + return values or None def search_obj_in_list(name, lst): @@ -188,6 +196,8 @@ commands.append('no ipv6 address {}'.format(ipv6)) else: commands.append('no ipv6 address') + if 'dhcp' in obj_in_have['ipv6']: + commands.append('no ipv6 address dhcp') elif state == 'present': if ipv4: @@ -198,7 +208,7 @@ commands.append('ip address {}'.format(ipv4)) if ipv6: - if obj_in_have is None or obj_in_have.get('ipv6') is None or ipv6.lower() != obj_in_have['ipv6'].lower(): + if obj_in_have is None or obj_in_have.get('ipv6') is None or ipv6.lower() not in [addr.lower() for addr in obj_in_have['ipv6']]: commands.append('ipv6 address {}'.format(ipv6)) if commands[-1] == interface: @@ -296,9 +306,6 @@ result = {'changed': False} - if warnings: - result['warnings'] = warnings - want = map_params_to_obj(module) have = map_config_to_obj(module) @@ -307,10 +314,14 @@ if commands: if not module.check_mode: - load_config(module, commands) + resp = load_config(module, commands) + warnings.extend((out for out in resp if out)) result['changed'] = True + if warnings: + result['warnings'] = warnings + module.exit_json(**result) diff -Nru ansible-2.5.3/lib/ansible/modules/network/ios/ios_vlan.py ansible-2.5.4/lib/ansible/modules/network/ios/ios_vlan.py --- ansible-2.5.3/lib/ansible/modules/network/ios/ios_vlan.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/ios/ios_vlan.py 2018-05-31 22:01:21.000000000 +0000 @@ -129,59 +129,59 @@ if state == 'absent': if obj_in_have: - commands.append('no vlan {}'.format(vlan_id)) + commands.append('no vlan {0}'.format(vlan_id)) elif state == 'present': if not obj_in_have: - commands.append('vlan {}'.format(vlan_id)) + commands.append('vlan {0}'.format(vlan_id)) if name: - commands.append('name {}'.format(name)) + commands.append('name {0}'.format(name)) if interfaces: for i in interfaces: - commands.append('interface {}'.format(i)) + commands.append('interface {0}'.format(i)) commands.append('switchport mode access') - commands.append('switchport access vlan {}'.format(vlan_id)) + commands.append('switchport access vlan {0}'.format(vlan_id)) else: if name: if name != obj_in_have['name']: - commands.append('vlan {}'.format(vlan_id)) - commands.append('name {}'.format(name)) + commands.append('vlan {0}'.format(vlan_id)) + commands.append('name {0}'.format(name)) if interfaces: if not obj_in_have['interfaces']: for i in interfaces: - commands.append('vlan {}'.format(vlan_id)) - commands.append('interface {}'.format(i)) + commands.append('vlan {0}'.format(vlan_id)) + commands.append('interface {0}'.format(i)) commands.append('switchport mode access') - commands.append('switchport access vlan {}'.format(vlan_id)) + commands.append('switchport access vlan {0}'.format(vlan_id)) elif set(interfaces) != set(obj_in_have['interfaces']): missing_interfaces = list(set(interfaces) - set(obj_in_have['interfaces'])) for i in missing_interfaces: - commands.append('vlan {}'.format(vlan_id)) - commands.append('interface {}'.format(i)) + commands.append('vlan {0}'.format(vlan_id)) + commands.append('interface {0}'.format(i)) commands.append('switchport mode access') - commands.append('switchport access vlan {}'.format(vlan_id)) + commands.append('switchport access vlan {0}'.format(vlan_id)) superfluous_interfaces = list(set(obj_in_have['interfaces']) - set(interfaces)) for i in superfluous_interfaces: - commands.append('vlan {}'.format(vlan_id)) - commands.append('interface {}'.format(i)) + commands.append('vlan {0}'.format(vlan_id)) + commands.append('interface {0}'.format(i)) commands.append('switchport mode access') - commands.append('no switchport access vlan {}'.format(vlan_id)) + commands.append('no switchport access vlan {0}'.format(vlan_id)) else: - commands.append('vlan {}'.format(vlan_id)) + commands.append('vlan {0}'.format(vlan_id)) if name: - commands.append('name {}'.format(name)) - commands.append('state {}'.format(state)) + commands.append('name {0}'.format(name)) + commands.append('state {0}'.format(state)) if purge: for h in have: obj_in_want = search_obj_in_list(h['vlan_id'], want) if not obj_in_want and h['vlan_id'] != '1': - commands.append('no vlan {}'.format(h['vlan_id'])) + commands.append('no vlan {0}'.format(h['vlan_id'])) return commands @@ -211,36 +211,46 @@ return obj -def map_config_to_obj(module): - output = run_commands(module, ['show vlan']) - lines = output[0].strip().splitlines()[2:-1] +def parse_to_logical_rows(out): + started_yielding = False + cur_row = [] + for l in out.splitlines()[2:]: + if not l: + """Skip empty lines.""" + continue + if '0' < l[0] < '9': + """Line starting with a number.""" + if started_yielding: + yield cur_row + cur_row = [] # Reset it to hold a next chunk + started_yielding = True + cur_row.append(l) + + # Return the rest of it: + yield cur_row + + +def map_ports_str_to_list(ports_str): + return list(filter(bool, (p.strip().replace('Gi', 'GigabitEthernet') for p in ports_str.split(', ')))) + + +def parse_to_obj(logical_rows): + first_row = logical_rows[0] + rest_rows = logical_rows[1:] + obj = re.match(r'(?P\d+)\s+(?P[^\s]+)\s+(?P[^\s]+)\s*(?P.*)', first_row).groupdict() + if obj['state'] == 'suspended': + obj['state'] = 'suspend' + obj['interfaces'] = map_ports_str_to_list(obj['interfaces']) + obj['interfaces'].extend(prts_r for prts in rest_rows for prts_r in map_ports_str_to_list(prts)) + return obj - if not lines: - return list() - objs = list() +def parse_vlan_brief(vlan_out): + return [parse_to_obj(r) for r in parse_to_logical_rows(vlan_out)] - for l in lines: - splitted_line = l.strip().replace(",", "").split() - if splitted_line == []: - break - obj = {} - obj['vlan_id'] = splitted_line[0] - obj['name'] = splitted_line[1] - obj['state'] = splitted_line[2] - - if obj['state'] == 'suspended': - obj['state'] = 'suspend' - - obj['interfaces'] = [] - if len(splitted_line) > 3: - interface = [] - for i in range(3, len(splitted_line)): - interface.append(splitted_line[i].replace('Gi', 'GigabitEthernet')) - obj['interfaces'].extend(interface) - objs.append(obj) - return objs +def map_config_to_obj(module): + return parse_vlan_brief(run_commands(module, ['show vlan brief'])[0]) def check_declarative_intent_params(want, module, result): diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_facts.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_facts.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_facts.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_facts.py 2018-05-31 22:01:21.000000000 +0000 @@ -499,7 +499,13 @@ return objects def parse_fan_info(self, data): - data = data['fandetails']['TABLE_faninfo']['ROW_faninfo'] + objects = list() + if data.get('fandetails'): + data = data['fandetails']['TABLE_faninfo']['ROW_faninfo'] + elif data.get('fandetails_3k'): + data = data['fandetails_3k']['TABLE_faninfo']['ROW_faninfo'] + else: + return objects objects = list(self.transform_iterable(data, self.FAN_MAP)) return objects diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_hsrp.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_hsrp.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_hsrp.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_hsrp.py 2018-05-31 22:01:21.000000000 +0000 @@ -148,6 +148,7 @@ from ansible.module_utils.network.nxos.nxos import load_config, run_commands from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec +from ansible.module_utils.network.nxos.nxos import get_interface_type from ansible.module_utils.basic import AnsibleModule @@ -187,23 +188,6 @@ return new_dict -def get_interface_type(interface): - if interface.upper().startswith('ET'): - return 'ethernet' - elif interface.upper().startswith('VL'): - return 'svi' - elif interface.upper().startswith('LO'): - return 'loopback' - elif interface.upper().startswith('MG'): - return 'management' - elif interface.upper().startswith('MA'): - return 'management' - elif interface.upper().startswith('PO'): - return 'portchannel' - else: - return 'unknown' - - def get_interface_mode(interface, intf_type, module): command = 'show interface {0}'.format(interface) interface = {} diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_igmp_interface.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_igmp_interface.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_igmp_interface.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_igmp_interface.py 2018-05-31 22:01:21.000000000 +0000 @@ -234,6 +234,7 @@ from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_commands from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, check_args +from ansible.module_utils.network.nxos.nxos import get_interface_type from ansible.module_utils.basic import AnsibleModule import re @@ -270,23 +271,6 @@ return mode -def get_interface_type(interface): - if interface.upper().startswith('ET'): - return 'ethernet' - elif interface.upper().startswith('VL'): - return 'svi' - elif interface.upper().startswith('LO'): - return 'loopback' - elif interface.upper().startswith('MG'): - return 'management' - elif interface.upper().startswith('MA'): - return 'management' - elif interface.upper().startswith('PO'): - return 'portchannel' - else: - return 'unknown' - - def apply_key_map(key_map, table): new_dict = {} for key, value in table.items(): diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_interface.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_interface.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_interface.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_interface.py 2018-05-31 22:01:21.000000000 +0000 @@ -1,20 +1,7 @@ #!/usr/bin/python -# -# 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 . -# +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], @@ -229,7 +216,8 @@ from copy import deepcopy from ansible.module_utils.network.nxos.nxos import load_config, run_commands -from ansible.module_utils.network.nxos.nxos import nxos_argument_spec +from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, normalize_interface +from ansible.module_utils.network.nxos.nxos import get_interface_type from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.network.common.utils import conditional, remove_default_spec @@ -258,27 +246,6 @@ return None -def get_interface_type(interface): - """Gets the type of interface - """ - if interface.upper().startswith('ET'): - return 'ethernet' - elif interface.upper().startswith('VL'): - return 'svi' - elif interface.upper().startswith('LO'): - return 'loopback' - elif interface.upper().startswith('MG'): - return 'management' - elif interface.upper().startswith('MA'): - return 'management' - elif interface.upper().startswith('PO'): - return 'portchannel' - elif interface.upper().startswith('NV'): - return 'nve' - else: - return 'unknown' - - def get_interfaces_dict(module): """Gets all active interfaces on a given switch """ @@ -307,46 +274,6 @@ return interfaces -def normalize_interface(name): - """Return the normalized interface name - """ - if not name: - return None - - def _get_number(name): - digits = '' - for char in name: - if char.isdigit() or char == '/': - digits += char - return digits - - if name.lower().startswith('et'): - if_type = 'Ethernet' - elif name.lower().startswith('vl'): - if_type = 'Vlan' - elif name.lower().startswith('lo'): - if_type = 'loopback' - elif name.lower().startswith('po'): - if_type = 'port-channel' - elif name.lower().startswith('nv'): - if_type = 'nve' - else: - if_type = None - - number_list = name.split(' ') - if len(number_list) == 2: - number = number_list[-1].strip() - else: - number = _get_number(name) - - if if_type: - proper_interface = if_type + number - else: - proper_interface = name - - return proper_interface - - def get_vlan_interface_attributes(name, intf_type, module): """ Returns dictionary that has two k/v pairs: admin_state & description if not an svi, returns None @@ -458,10 +385,12 @@ elif state == 'present': if obj_in_have: - if mode == 'layer2' and mode != obj_in_have.get('mode'): - add_command_to_interface(interface, 'switchport', commands) - elif mode == 'layer3' and mode != obj_in_have.get('mode'): - add_command_to_interface(interface, 'no switchport', commands) + # Don't run switchport command for loopback and svi interfaces + if get_interface_type(name) in ('ethernet', 'portchannel'): + if mode == 'layer2' and mode != obj_in_have.get('mode'): + add_command_to_interface(interface, 'switchport', commands) + elif mode == 'layer3' and mode != obj_in_have.get('mode'): + add_command_to_interface(interface, 'no switchport', commands) if admin_state == 'up' and admin_state != obj_in_have.get('admin_state'): add_command_to_interface(interface, 'no shutdown', commands) @@ -498,10 +427,12 @@ else: commands.append(interface) - if mode == 'layer2': - commands.append('switchport') - elif mode == 'layer3': - commands.append('no switchport') + # Don't run switchport command for loopback and svi interfaces + if get_interface_type(name) in ('ethernet', 'portchannel'): + if mode == 'layer2': + commands.append('switchport') + elif mode == 'layer3': + commands.append('no switchport') if admin_state == 'up': commands.append('no shutdown') diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/_nxos_ip_interface.py ansible-2.5.4/lib/ansible/modules/network/nxos/_nxos_ip_interface.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/_nxos_ip_interface.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/_nxos_ip_interface.py 2018-05-31 22:01:21.000000000 +0000 @@ -187,6 +187,7 @@ from ansible.module_utils.network.nxos.nxos import load_config, run_commands from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec +from ansible.module_utils.network.nxos.nxos import get_interface_type from ansible.module_utils.basic import AnsibleModule @@ -217,23 +218,6 @@ return body -def get_interface_type(interface): - if interface.upper().startswith('ET'): - return 'ethernet' - elif interface.upper().startswith('VL'): - return 'svi' - elif interface.upper().startswith('LO'): - return 'loopback' - elif interface.upper().startswith('MG'): - return 'management' - elif interface.upper().startswith('MA'): - return 'management' - elif interface.upper().startswith('PO'): - return 'portchannel' - else: - return 'unknown' - - def is_default(interface, module): command = 'show run interface {0}'.format(interface) diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_l2_interface.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_l2_interface.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_l2_interface.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_l2_interface.py 2018-05-31 22:01:21.000000000 +0000 @@ -120,36 +120,11 @@ from copy import deepcopy from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_commands -from ansible.module_utils.network.nxos.nxos import nxos_argument_spec +from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, get_interface_type from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.network.common.utils import remove_default_spec -def get_interface_type(name): - """Gets the type of interface - Args: - interface (str): full name of interface, i.e. Ethernet1/1, loopback10, - port-channel20, vlan20 - Returns: - type of interface: ethernet, svi, loopback, management, portchannel, - or unknown - """ - if name.upper().startswith('ET'): - return 'ethernet' - elif name.upper().startswith('VL'): - return 'svi' - elif name.upper().startswith('LO'): - return 'loopback' - elif name.upper().startswith('MG'): - return 'management' - elif name.upper().startswith('MA'): - return 'management' - elif name.upper().startswith('PO'): - return 'portchannel' - else: - return 'unknown' - - def get_interface_mode(name, module): """Gets current mode of interface: layer2 or layer3 Args: diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_l3_interface.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_l3_interface.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_l3_interface.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_l3_interface.py 2018-05-31 22:01:21.000000000 +0000 @@ -90,7 +90,7 @@ from ansible.module_utils.network.common.config import CustomNetworkConfig from ansible.module_utils.network.common.utils import remove_default_spec from ansible.module_utils.network.nxos.nxos import get_config, load_config -from ansible.module_utils.network.nxos.nxos import nxos_argument_spec +from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, normalize_interface def search_obj_in_list(name, lst): @@ -154,10 +154,14 @@ if item.get(key) is None: item[key] = module.params[key] - obj.append(item.copy()) + d = item.copy() + name = d['name'] + d['name'] = normalize_interface(name) + obj.append(d) + else: obj.append({ - 'name': module.params['name'], + 'name': normalize_interface(module.params['name']), 'ipv4': module.params['ipv4'], 'ipv6': module.params['ipv6'], 'state': module.params['state'] @@ -178,7 +182,7 @@ if config: match_name = re.findall(r'interface (\S+)', config, re.M) if match_name: - obj['name'] = match_name[0] + obj['name'] = normalize_interface(match_name[0]) match_ipv4 = re.findall(r'ip address (\S+)', config, re.M) if match_ipv4: diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_linkagg.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_linkagg.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_linkagg.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_linkagg.py 2018-05-31 22:01:21.000000000 +0000 @@ -134,50 +134,11 @@ from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_commands from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec +from ansible.module_utils.network.nxos.nxos import normalize_interface from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.network.common.utils import remove_default_spec -def normalize_interface(name): - """Return the normalized interface name - """ - if not name: - return - - def _get_number(name): - digits = '' - for char in name: - if char.isdigit() or char in '/.': - digits += char - return digits - - if name.lower().startswith('et'): - if_type = 'Ethernet' - elif name.lower().startswith('vl'): - if_type = 'Vlan' - elif name.lower().startswith('lo'): - if_type = 'loopback' - elif name.lower().startswith('po'): - if_type = 'port-channel' - elif name.lower().startswith('nv'): - if_type = 'nve' - else: - if_type = None - - number_list = name.split(' ') - if len(number_list) == 2: - number = number_list[-1].strip() - else: - number = _get_number(name) - - if if_type: - proper_interface = if_type + number - else: - proper_interface = name - - return proper_interface - - def execute_show_command(command, module): device_info = get_capabilities(module) network_api = device_info.get('network_api', 'nxapi') diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_pim_interface.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_pim_interface.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_pim_interface.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_pim_interface.py 2018-05-31 22:01:21.000000000 +0000 @@ -154,6 +154,7 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.network.nxos.nxos import get_config, load_config, run_commands from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, check_args +from ansible.module_utils.network.nxos.nxos import get_interface_type from ansible.module_utils.six import string_types @@ -219,23 +220,6 @@ return gexisting, jp_bidir, isauth -def get_interface_type(interface): - if interface.upper().startswith('ET'): - return 'ethernet' - elif interface.upper().startswith('VL'): - return 'svi' - elif interface.upper().startswith('LO'): - return 'loopback' - elif interface.upper().startswith('MG'): - return 'management' - elif interface.upper().startswith('MA'): - return 'management' - elif interface.upper().startswith('PO'): - return 'portchannel' - else: - return 'unknown' - - def get_interface_mode(interface, intf_type, module): mode = 'unknown' command = 'show interface {0}'.format(interface) diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/_nxos_switchport.py ansible-2.5.4/lib/ansible/modules/network/nxos/_nxos_switchport.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/_nxos_switchport.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/_nxos_switchport.py 2018-05-31 22:01:21.000000000 +0000 @@ -132,34 +132,10 @@ from ansible.module_utils.network.nxos.nxos import load_config, run_commands from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec +from ansible.module_utils.network.nxos.nxos import get_interface_type from ansible.module_utils.basic import AnsibleModule -def get_interface_type(interface): - """Gets the type of interface - Args: - interface (str): full name of interface, i.e. Ethernet1/1, loopback10, - port-channel20, vlan20 - Returns: - type of interface: ethernet, svi, loopback, management, portchannel, - or unknown - """ - if interface.upper().startswith('ET'): - return 'ethernet' - elif interface.upper().startswith('VL'): - return 'svi' - elif interface.upper().startswith('LO'): - return 'loopback' - elif interface.upper().startswith('MG'): - return 'management' - elif interface.upper().startswith('MA'): - return 'management' - elif interface.upper().startswith('PO'): - return 'portchannel' - else: - return 'unknown' - - def get_interface_mode(interface, module): """Gets current mode of interface: layer2 or layer3 Args: diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_vlan.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_vlan.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_vlan.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_vlan.py 2018-05-31 22:01:21.000000000 +0000 @@ -467,21 +467,31 @@ return str(vni) +def get_vlan_int(interfaces): + vlan_int = [] + for i in interfaces.split(','): + if 'eth' in i.lower() and '-' in i: + int_range = i.split('-') + stop = int((int_range)[1]) + start = int(int_range[0].split('/')[1]) + eth = int_range[0].split('/')[0] + for r in range(start, stop + 1): + vlan_int.append(eth + '/' + str(r)) + else: + vlan_int.append(i) + return vlan_int + + def parse_interfaces(module, vlan): vlan_int = [] interfaces = vlan.get('vlanshowplist-ifidx') if interfaces: - for i in interfaces.split(','): - if 'eth' in i.lower() and '-' in i: - int_range = i.split('-') - stop = int((int_range)[1]) - start = int(int_range[0].split('/')[1]) - eth = int_range[0].split('/')[0] - for r in range(start, stop + 1): - vlan_int.append(eth + '/' + str(r)) - else: - vlan_int.append(i) - + if isinstance(interfaces, list): + interfaces_list = [i.strip() for i in interfaces] + interfaces_str = ','.join(interfaces_list) + vlan_int = get_vlan_int(interfaces_str) + else: + vlan_int = get_vlan_int(interfaces) return vlan_int diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_vrf_interface.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_vrf_interface.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_vrf_interface.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_vrf_interface.py 2018-05-31 22:01:21.000000000 +0000 @@ -82,6 +82,7 @@ from ansible.module_utils.network.nxos.nxos import load_config, run_commands from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec +from ansible.module_utils.network.nxos.nxos import get_interface_type from ansible.module_utils.basic import AnsibleModule @@ -97,23 +98,6 @@ return run_commands(module, cmds)[0] -def get_interface_type(interface): - if interface.upper().startswith('ET'): - return 'ethernet' - elif interface.upper().startswith('VL'): - return 'svi' - elif interface.upper().startswith('LO'): - return 'loopback' - elif interface.upper().startswith('MG'): - return 'management' - elif interface.upper().startswith('MA'): - return 'management' - elif interface.upper().startswith('PO'): - return 'portchannel' - else: - return 'unknown' - - def get_interface_mode(interface, intf_type, module): command = 'show interface {0}'.format(interface) interface = {} diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_vrf.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_vrf.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_vrf.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_vrf.py 2018-05-31 22:01:21.000000000 +0000 @@ -182,7 +182,7 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.network.nxos.nxos import load_config, run_commands -from ansible.module_utils.network.nxos.nxos import nxos_argument_spec +from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, get_interface_type from ansible.module_utils.network.common.utils import remove_default_spec @@ -269,7 +269,8 @@ if interfaces and interfaces[0] != 'default': for i in interfaces: commands.append('interface {0}'.format(i)) - commands.append('no switchport') + if get_interface_type(i) in ('ethernet', 'portchannel'): + commands.append('no switchport') commands.append('vrf member {0}'.format(name)) else: @@ -303,7 +304,8 @@ commands.append('vrf context {0}'.format(name)) commands.append('exit') commands.append('interface {0}'.format(i)) - commands.append('no switchport') + if get_interface_type(i) in ('ethernet', 'portchannel'): + commands.append('no switchport') commands.append('vrf member {0}'.format(name)) elif set(interfaces) != set(obj_in_have['interfaces']): @@ -312,7 +314,8 @@ commands.append('vrf context {0}'.format(name)) commands.append('exit') commands.append('interface {0}'.format(i)) - commands.append('no switchport') + if get_interface_type(i) in ('ethernet', 'portchannel'): + commands.append('no switchport') commands.append('vrf member {0}'.format(name)) superfluous_interfaces = list(set(obj_in_have['interfaces']) - set(interfaces)) @@ -320,7 +323,8 @@ commands.append('vrf context {0}'.format(name)) commands.append('exit') commands.append('interface {0}'.format(i)) - commands.append('no switchport') + if get_interface_type(i) in ('ethernet', 'portchannel'): + commands.append('no switchport') commands.append('no vrf member {0}'.format(name)) elif interfaces and interfaces[0] == 'default': if obj_in_have['interfaces']: @@ -328,7 +332,8 @@ commands.append('vrf context {0}'.format(name)) commands.append('exit') commands.append('interface {0}'.format(i)) - commands.append('no switchport') + if get_interface_type(i) in ('ethernet', 'portchannel'): + commands.append('no switchport') commands.append('no vrf member {0}'.format(name)) if purge: diff -Nru ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_vrrp.py ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_vrrp.py --- ansible-2.5.3/lib/ansible/modules/network/nxos/nxos_vrrp.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/network/nxos/nxos_vrrp.py 2018-05-31 22:01:21.000000000 +0000 @@ -118,6 +118,7 @@ from ansible.module_utils.network.nxos.nxos import load_config, run_commands from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argument_spec +from ansible.module_utils.network.nxos.nxos import get_interface_type from ansible.module_utils.basic import AnsibleModule @@ -146,23 +147,6 @@ return new_dict -def get_interface_type(interface): - if interface.upper().startswith('ET'): - return 'ethernet' - elif interface.upper().startswith('VL'): - return 'svi' - elif interface.upper().startswith('LO'): - return 'loopback' - elif interface.upper().startswith('MG'): - return 'management' - elif interface.upper().startswith('MA'): - return 'management' - elif interface.upper().startswith('PO'): - return 'portchannel' - else: - return 'unknown' - - def is_default(interface, module): command = 'show run interface {0}'.format(interface) diff -Nru ansible-2.5.3/lib/ansible/modules/packaging/os/apt.py ansible-2.5.4/lib/ansible/modules/packaging/os/apt.py --- ansible-2.5.3/lib/ansible/modules/packaging/os/apt.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/packaging/os/apt.py 2018-05-31 22:01:21.000000000 +0000 @@ -473,7 +473,13 @@ if not packages: return - apt_mark_cmd_path = m.get_bin_path("apt-mark", required=True) + apt_mark_cmd_path = m.get_bin_path("apt-mark") + + # https://github.com/ansible/ansible/issues/40531 + if apt_mark_cmd_path is None: + m.warn("Could not find apt-mark binary, not marking package(s) as manually installed.") + return + cmd = "%s manual %s" % (apt_mark_cmd_path, ' '.join(packages)) rc, out, err = m.run_command(cmd) diff -Nru ansible-2.5.3/lib/ansible/modules/source_control/git.py ansible-2.5.4/lib/ansible/modules/source_control/git.py --- ansible-2.5.3/lib/ansible/modules/source_control/git.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/source_control/git.py 2018-05-31 22:01:21.000000000 +0000 @@ -620,6 +620,30 @@ return False +def get_repo_path(dest, bare): + if bare: + repo_path = dest + else: + repo_path = os.path.join(dest, '.git') + # Check if the .git is a file. If it is a file, it means that the repository is in external directory respective to the working copy (e.g. we are in a + # submodule structure). + if os.path.isfile(repo_path): + with open(repo_path, 'r') as gitfile: + data = gitfile.read() + ref_prefix, gitdir = data.rstrip().split('gitdir: ', 1) + if ref_prefix: + raise ValueError('.git file has invalid git dir reference format') + + # There is a possibility the .git file to have an absolute path. + if os.path.isabs(gitdir): + repo_path = gitdir + else: + repo_path = os.path.join(repo_path.split('.git')[0], gitdir) + if not os.path.isdir(repo_path): + raise ValueError('%s is not a directory' % repo_path) + return repo_path + + def get_head_branch(git_path, module, dest, remote, bare=False): ''' Determine what branch HEAD is associated with. This is partly @@ -628,31 +652,16 @@ associated with. In the case of a detached HEAD, this will look up the branch in .git/refs/remotes//HEAD. ''' - if bare: - repo_path = dest - else: - repo_path = os.path.join(dest, '.git') - # Check if the .git is a file. If it is a file, it means that we are in a submodule structure. - if os.path.isfile(repo_path): - try: - git_conf = open(repo_path, 'rb') - for line in git_conf: - config_val = line.split(b(':'), 1) - if config_val[0].strip() == b('gitdir'): - gitdir = to_native(config_val[1].strip(), errors='surrogate_or_strict') - break - else: - # No repo path found - return '' - - # There is a possibility the .git file to have an absolute path. - if os.path.isabs(gitdir): - repo_path = gitdir - else: - repo_path = os.path.join(repo_path.split('.git')[0], gitdir) - except (IOError, AttributeError): - # No repo path found - return '' + try: + repo_path = get_repo_path(dest, bare) + except (IOError, ValueError) as err: + # No repo path found + """``.git`` file does not have a valid format for detached Git dir.""" + module.fail_json( + msg='Current repo does not have a valid reference to a ' + 'separate Git dir or it refers to the invalid path', + details=str(err), + ) # Read .git/HEAD for the name of the branch. # If we're in a detached HEAD state, look up the branch associated with # the remote HEAD in .git/refs/remotes//HEAD @@ -1042,10 +1051,17 @@ module.fail_json(msg="the destination directory must be specified unless clone=no") elif dest: dest = os.path.abspath(dest) - if bare: - gitconfig = os.path.join(dest, 'config') - else: - gitconfig = os.path.join(dest, '.git', 'config') + try: + repo_path = get_repo_path(dest, bare) + except (IOError, ValueError) as err: + # No repo path found + """``.git`` file does not have a valid format for detached Git dir.""" + module.fail_json( + msg='Current repo does not have a valid reference to a ' + 'separate Git dir or it refers to the invalid path', + details=str(err), + ) + gitconfig = os.path.join(repo_path, 'config') # create a wrapper script and export # GIT_SSH= as an environment variable diff -Nru ansible-2.5.3/lib/ansible/modules/system/iptables.py ansible-2.5.4/lib/ansible/modules/system/iptables.py --- ansible-2.5.3/lib/ansible/modules/system/iptables.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/system/iptables.py 2018-05-31 22:01:21.000000000 +0000 @@ -106,11 +106,15 @@ description: - TCP flags specification. - C(tcp_flags) expects a dict with the two keys C(flags) and C(flags_set). - - The C(flags) list is the mask, a list of flags you want to examine. - - The C(flags_set) list tells which one(s) should be set. - If one of the two values is missing, the --tcp-flags option will be ignored. default: {} version_added: "2.4" + suboptions: + flags: + description: + - List of flags you want to examine. + flags_set: + description: + - Flags to be set. match: description: - Specifies a match to use, that is, an extension module that tests for @@ -340,6 +344,19 @@ protocol: tcp reject_with: tcp-reset ip_version: ipv4 + +# Set tcp flags +- iptables: + chain: OUTPUT + jump: DROP + protocol: tcp + tcp_flags: + flags: ALL + flags_set: + - ACK + - RST + - SYN + - FIN ''' import re @@ -518,7 +535,11 @@ destination=dict(type='str'), to_destination=dict(type='str'), match=dict(type='list', default=[]), - tcp_flags=dict(type='dict', default={}), + tcp_flags=dict(type='dict', + options=dict( + flags=dict(type='list'), + flags_set=dict(type='list')) + ), jump=dict(type='str'), log_prefix=dict(type='str'), goto=dict(type='str'), @@ -605,5 +626,6 @@ module.exit_json(**args) + if __name__ == '__main__': main() diff -Nru ansible-2.5.3/lib/ansible/modules/system/known_hosts.py ansible-2.5.4/lib/ansible/modules/system/known_hosts.py --- ansible-2.5.3/lib/ansible/modules/system/known_hosts.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/system/known_hosts.py 2018-05-31 22:01:21.000000000 +0000 @@ -179,6 +179,11 @@ # The approach is to write the key to a temporary file, # and then attempt to look up the specified host in that file. + + if re.search(r'\S+(\s+)?,(\s+)?', host): + module.fail_json(msg="Comma separated list of names is not supported. " + "Please pass a single name to lookup in the known_hosts file.") + try: outf = tempfile.NamedTemporaryFile(mode='w+') outf.write(key) @@ -188,7 +193,7 @@ (outf.name, to_native(e))) sshkeygen_command = [sshkeygen, '-F', host, '-f', outf.name] - rc, stdout, stderr = module.run_command(sshkeygen_command, check_rc=True) + rc, stdout, stderr = module.run_command(sshkeygen_command) try: outf.close() except: diff -Nru ansible-2.5.3/lib/ansible/modules/system/user.py ansible-2.5.4/lib/ansible/modules/system/user.py --- ansible-2.5.3/lib/ansible/modules/system/user.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/system/user.py 2018-05-31 22:01:21.000000000 +0000 @@ -218,6 +218,7 @@ expires: 1422403387 ''' +import errno import grp import os import platform @@ -616,6 +617,13 @@ return passwd, expires except KeyError: return passwd, expires + except OSError as e: + # Python 3.6 raises PermissionError instead of KeyError + # Due to absence of PermissionError in python2.7 need to check + # errno + if e.errno in (errno.EACCES, errno.EPERM): + return passwd, expires + raise if not self.user_exists(): return passwd, expires diff -Nru ansible-2.5.3/lib/ansible/modules/utilities/helper/meta.py ansible-2.5.4/lib/ansible/modules/utilities/helper/meta.py --- ansible-2.5.3/lib/ansible/modules/utilities/helper/meta.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/utilities/helper/meta.py 2018-05-31 22:01:21.000000000 +0000 @@ -35,7 +35,7 @@ - "C(noop) (added in 2.0) This literally does 'nothing'. It is mainly used internally and not recommended for general use." - "C(clear_facts) (added in 2.1) causes the gathered facts for the hosts specified in the play's list of hosts to be cleared, including the fact cache." - "C(clear_host_errors) (added in 2.1) clears the failed state (if any) from hosts specified in the play's list of hosts." - - "C(end_play) (added in 2.2) causes the play to end without failing the host." + - "C(end_play) (added in 2.2) causes the play to end without failing the host(s). Note that this affects all hosts." - "C(reset_connection) (added in 2.3) interrupts a persistent connection (i.e. ssh + control persist)" choices: ['noop', 'flush_handlers', 'refresh_inventory', 'clear_facts', 'clear_host_errors', 'end_play', 'reset_connection'] required: true diff -Nru ansible-2.5.3/lib/ansible/modules/web_infrastructure/jenkins_plugin.py ansible-2.5.4/lib/ansible/modules/web_infrastructure/jenkins_plugin.py --- ansible-2.5.3/lib/ansible/modules/web_infrastructure/jenkins_plugin.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/web_infrastructure/jenkins_plugin.py 2018-05-31 22:01:21.000000000 +0000 @@ -275,7 +275,7 @@ sample: "present" ''' -from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import AnsibleModule, to_bytes from ansible.module_utils.six.moves.urllib.parse import urlencode from ansible.module_utils.urls import fetch_url, url_argument_spec from ansible.module_utils._text import to_native @@ -505,7 +505,7 @@ sha1sum_old = base64.b64encode(sha1_old.digest()) # If the latest version changed, download it - if sha1sum_old != plugin_data['sha1']: + if sha1sum_old != to_bytes(plugin_data['sha1']): if not self.module.check_mode: r = self._download_plugin(plugin_url) self._write_file(plugin_file, r) diff -Nru ansible-2.5.3/lib/ansible/modules/windows/win_environment.ps1 ansible-2.5.4/lib/ansible/modules/windows/win_environment.ps1 --- ansible-2.5.3/lib/ansible/modules/windows/win_environment.ps1 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/windows/win_environment.ps1 2018-05-31 22:01:21.000000000 +0000 @@ -28,6 +28,8 @@ if ($state -eq "absent" -and $value) { Add-Warning -obj $result -message "When removing environment variable '$name' it should not have a value '$value' set" $value = $null +} elseif ($state -eq "present" -and (-not $value)) { + Fail-Json -obj $result -message "When state=present, value must be defined and not an empty string, if you wish to remove the envvar, set state=absent" } if ($state -eq "present" -and $before_value -ne $value) { diff -Nru ansible-2.5.3/lib/ansible/modules/windows/win_environment.py ansible-2.5.4/lib/ansible/modules/windows/win_environment.py --- ansible-2.5.3/lib/ansible/modules/windows/win_environment.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/windows/win_environment.py 2018-05-31 22:01:21.000000000 +0000 @@ -30,6 +30,7 @@ value: description: - The value to store in the environment variable. + - Must be set when C(state=present) and cannot be an empty string. - Can be omitted for C(state=absent). level: description: diff -Nru ansible-2.5.3/lib/ansible/modules/windows/win_get_url.ps1 ansible-2.5.4/lib/ansible/modules/windows/win_get_url.ps1 --- ansible-2.5.3/lib/ansible/modules/windows/win_get_url.ps1 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/windows/win_get_url.ps1 2018-05-31 22:01:21.000000000 +0000 @@ -53,9 +53,9 @@ if ($credentials) { if ($force_basic_auth) { - $extWebClient.Headers.Add("Authorization","Basic $credentials") + $webRequest.Headers.Add("Authorization", "Basic $credentials") } else { - $extWebClient.Credentials = $credentials + $webRequest.Credentials = $credentials } } diff -Nru ansible-2.5.3/lib/ansible/modules/windows/win_scheduled_task.py ansible-2.5.4/lib/ansible/modules/windows/win_scheduled_task.py --- ansible-2.5.3/lib/ansible/modules/windows/win_scheduled_task.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/modules/windows/win_scheduled_task.py 2018-05-31 22:01:21.000000000 +0000 @@ -440,7 +440,7 @@ arguments: /c whoami triggers: - type: daily - start_boundary: 2017-10-09T09:00:00 + start_boundary: '2017-10-09T09:00:00' username: SYSTEM state: present enabled: yes diff -Nru ansible-2.5.3/lib/ansible/module_utils/network/ios/ios.py ansible-2.5.4/lib/ansible/module_utils/network/ios/ios.py --- ansible-2.5.3/lib/ansible/module_utils/network/ios/ios.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/module_utils/network/ios/ios.py 2018-05-31 22:01:21.000000000 +0000 @@ -165,4 +165,4 @@ def load_config(module, commands): connection = get_connection(module) - out = connection.edit_config(commands) + return connection.edit_config(commands) diff -Nru ansible-2.5.3/lib/ansible/module_utils/network/nxos/nxos.py ansible-2.5.4/lib/ansible/module_utils/network/nxos/nxos.py --- ansible-2.5.3/lib/ansible/module_utils/network/nxos/nxos.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/lib/ansible/module_utils/network/nxos/nxos.py 2018-05-31 22:01:21.000000000 +0000 @@ -478,3 +478,64 @@ def get_capabilities(module): conn = get_connection(module) return conn.get_capabilities() + + +def normalize_interface(name): + """Return the normalized interface name + """ + if not name: + return + + def _get_number(name): + digits = '' + for char in name: + if char.isdigit() or char in '/.': + digits += char + return digits + + if name.lower().startswith('et'): + if_type = 'Ethernet' + elif name.lower().startswith('vl'): + if_type = 'Vlan' + elif name.lower().startswith('lo'): + if_type = 'loopback' + elif name.lower().startswith('po'): + if_type = 'port-channel' + elif name.lower().startswith('nv'): + if_type = 'nve' + else: + if_type = None + + number_list = name.split(' ') + if len(number_list) == 2: + number = number_list[-1].strip() + else: + number = _get_number(name) + + if if_type: + proper_interface = if_type + number + else: + proper_interface = name + + return proper_interface + + +def get_interface_type(interface): + """Gets the type of interface + """ + if interface.upper().startswith('ET'): + return 'ethernet' + elif interface.upper().startswith('VL'): + return 'svi' + elif interface.upper().startswith('LO'): + return 'loopback' + elif interface.upper().startswith('MG'): + return 'management' + elif interface.upper().startswith('MA'): + return 'management' + elif interface.upper().startswith('PO'): + return 'portchannel' + elif interface.upper().startswith('NV'): + return 'nve' + else: + return 'unknown' diff -Nru ansible-2.5.3/lib/ansible/playbook/play_context.py ansible-2.5.4/lib/ansible/playbook/play_context.py --- ansible-2.5.3/lib/ansible/playbook/play_context.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/playbook/play_context.py 2018-05-31 22:01:21.000000000 +0000 @@ -544,7 +544,7 @@ flags += ' -u %s ' % self.become_user # FIXME: make shell independent - becomecmd = '%s %s echo %s && %s %s env ANSIBLE=true %s' % (exe, flags, success_key, exe, flags, cmd) + becomecmd = '%s %s %s -c %s' % (exe, flags, executable, success_cmd) elif self.become_method == 'dzdo': diff -Nru ansible-2.5.3/lib/ansible/plugins/action/edgeos_config.py ansible-2.5.4/lib/ansible/plugins/action/edgeos_config.py --- ansible-2.5.3/lib/ansible/plugins/action/edgeos_config.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/action/edgeos_config.py 2018-05-31 22:01:21.000000000 +0000 @@ -35,6 +35,8 @@ class ActionModule(_ActionModule): def run(self, tmp=None, task_vars=None): + if self._play_context.connection != 'network_cli': + return {'failed': True, 'msg': 'Connection type %s is not valid for this module. Must use network_cli.' % self._play_context.connection} if self._task.args.get('src'): try: diff -Nru ansible-2.5.3/lib/ansible/plugins/action/__init__.py ansible-2.5.4/lib/ansible/plugins/action/__init__.py --- ansible-2.5.3/lib/ansible/plugins/action/__init__.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/action/__init__.py 2018-05-31 22:01:21.000000000 +0000 @@ -583,7 +583,7 @@ expand_path = '~%s' % self._play_context.become_user else: # use remote user instead, if none set default to current user - expand_path = '~%s' % self._play_context.remote_user or self._connection.default_user or '' + expand_path = '~%s' % (self._play_context.remote_user or self._connection.default_user or '') # use shell to construct appropriate command and execute cmd = self._connection._shell.expand_user(expand_path) diff -Nru ansible-2.5.3/lib/ansible/plugins/action/pause.py ansible-2.5.4/lib/ansible/plugins/action/pause.py --- ansible-2.5.3/lib/ansible/plugins/action/pause.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/action/pause.py 2018-05-31 22:01:21.000000000 +0000 @@ -19,14 +19,16 @@ import datetime import signal +import sys import termios import time import tty from os import isatty from ansible.errors import AnsibleError +from ansible.module_utils._text import to_text, to_native +from ansible.module_utils.parsing.convert_bool import boolean from ansible.module_utils.six import PY3 -from ansible.module_utils._text import to_text from ansible.plugins.action import ActionBase try: @@ -35,6 +37,20 @@ from ansible.utils.display import Display display = Display() +try: + import curses + curses.setupterm() + HAS_CURSES = True +except (ImportError, curses.error): + HAS_CURSES = False + +if HAS_CURSES: + MOVE_TO_BOL = curses.tigetstr('cr') + CLEAR_TO_EOL = curses.tigetstr('el') +else: + MOVE_TO_BOL = b'\r' + CLEAR_TO_EOL = b'\x1b[K' + class AnsibleTimeoutExceeded(Exception): pass @@ -44,6 +60,11 @@ raise AnsibleTimeoutExceeded +def clear_line(stdout): + stdout.write(b'\x1b[%s' % MOVE_TO_BOL) + stdout.write(b'\x1b[%s' % CLEAR_TO_EOL) + + class ActionModule(ActionBase): ''' pauses execution for a length or time, or until input is received ''' @@ -81,10 +102,11 @@ # Should keystrokes be echoed to stdout? if 'echo' in self._task.args: - echo = self._task.args['echo'] - if not type(echo) == bool: + try: + echo = boolean(self._task.args['echo']) + except TypeError as e: result['failed'] = True - result['msg'] = "'%s' is not a valid setting for 'echo'." % self._task.args['echo'] + result['msg'] = to_native(e) return result # Add a note saying the output is hidden if echo is disabled @@ -96,7 +118,7 @@ prompt = "[%s]\n%s%s:" % (self._task.get_name().strip(), self._task.args['prompt'], echo_prompt) else: # If no custom prompt is specified, set a default prompt - prompt = "[%s]\n%s%s:" % (self._task.get_name().strip(), 'Press enter to continue', echo_prompt) + prompt = "[%s]\n%s%s:" % (self._task.get_name().strip(), 'Press enter to continue, Ctrl+C to interrupt', echo_prompt) # Are 'minutes' or 'seconds' keys that exist in 'args'? if 'minutes' in self._task.args or 'seconds' in self._task.args: @@ -149,65 +171,83 @@ try: if PY3: stdin = self._connection._new_stdin.buffer + stdout = sys.stdout.buffer else: stdin = self._connection._new_stdin + stdout = sys.stdout fd = stdin.fileno() except (ValueError, AttributeError): # ValueError: someone is using a closed file descriptor as stdin # AttributeError: someone is using a null file descriptor as stdin on windoez stdin = None + if fd is not None: if isatty(fd): + + # grab actual Ctrl+C sequence + try: + intr = termios.tcgetattr(fd)[6][termios.VINTR] + except Exception: + # unsupported/not present, use default + intr = b'\x03' # value for Ctrl+C + + # get backspace sequences + try: + backspace = termios.tcgetattr(fd)[6][termios.VERASE] + except Exception: + backspace = [b'\x7f', b'\x08'] + old_settings = termios.tcgetattr(fd) tty.setraw(fd) + tty.setraw(stdout.fileno()) - # Enable a few things turned off by tty.setraw() - # ICANON -> Allows characters to be deleted and hides things like ^M. - # ICRNL -> Makes the return key work when ICANON is enabled, otherwise - # you get stuck at the prompt with no way to get out of it. - # See man termios for details on these flags - if not seconds: + # Only echo input if no timeout is specified + if not seconds and echo: new_settings = termios.tcgetattr(fd) - new_settings[0] = new_settings[0] | termios.ICRNL - new_settings[3] = new_settings[3] | termios.ICANON + new_settings[3] = new_settings[3] | termios.ECHO termios.tcsetattr(fd, termios.TCSANOW, new_settings) - if echo: - # Enable ECHO since tty.setraw() disables it - new_settings = termios.tcgetattr(fd) - new_settings[3] = new_settings[3] | termios.ECHO - termios.tcsetattr(fd, termios.TCSANOW, new_settings) - # flush the buffer to make sure no previous key presses # are read in below termios.tcflush(stdin, termios.TCIFLUSH) + while True: try: if fd is not None: key_pressed = stdin.read(1) - - if seconds: - if key_pressed == b'\x03': - raise KeyboardInterrupt + if key_pressed == intr: # value for Ctrl+C + clear_line(stdout) + raise KeyboardInterrupt if not seconds: if fd is None or not isatty(fd): - display.warning("Not waiting from prompt as stdin is not interactive") + display.warning("Not waiting for response to prompt as stdin is not interactive") break + # read key presses and act accordingly if key_pressed in (b'\r', b'\n'): + clear_line(stdout) break + elif key_pressed in backspace: + # delete a character if backspace is pressed + result['user_input'] = result['user_input'][:-1] + clear_line(stdout) + if echo: + stdout.write(result['user_input']) + stdout.flush() else: result['user_input'] += key_pressed except KeyboardInterrupt: - if seconds is not None: - signal.alarm(0) + signal.alarm(0) display.display("Press 'C' to continue the play or 'A' to abort \r"), if self._c_or_a(stdin): + clear_line(stdout) break - else: - raise AnsibleError('user requested abort!') + + clear_line(stdout) + + raise AnsibleError('user requested abort!') except AnsibleTimeoutExceeded: # this is the exception we expect when the alarm signal diff -Nru ansible-2.5.3/lib/ansible/plugins/action/synchronize.py ansible-2.5.4/lib/ansible/plugins/action/synchronize.py --- ansible-2.5.3/lib/ansible/plugins/action/synchronize.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/action/synchronize.py 2018-05-31 22:01:21.000000000 +0000 @@ -301,6 +301,9 @@ new_connection = connection_loader.get('local', self._play_context, new_stdin) self._connection = new_connection + # Override _remote_is_local as an instance attribute specifically for the synchronize use case + # ensuring we set local tmpdir correctly + self._connection._remote_is_local = True self._override_module_replaced_vars(task_vars) # SWITCH SRC AND DEST HOST PER MODE diff -Nru ansible-2.5.3/lib/ansible/plugins/cliconf/__init__.py ansible-2.5.4/lib/ansible/plugins/cliconf/__init__.py --- ansible-2.5.3/lib/ansible/plugins/cliconf/__init__.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/cliconf/__init__.py 2018-05-31 22:01:21.000000000 +0000 @@ -185,7 +185,7 @@ ssh = self._connection.paramiko_conn._connect_uncached() if proto == 'scp': if not HAS_SCP: - self._connection.internal_error("Required library scp is not installed. Please install it using `pip install scp`") + raise AnsibleError("Required library scp is not installed. Please install it using `pip install scp`") with SCPClient(ssh.get_transport()) as scp: scp.put(source, destination) elif proto == 'sftp': @@ -197,7 +197,7 @@ ssh = self._connection.paramiko_conn._connect_uncached() if proto == 'scp': if not HAS_SCP: - self._connection.internal_error("Required library scp is not installed. Please install it using `pip install scp`") + raise AnsibleError("Required library scp is not installed. Please install it using `pip install scp`") with SCPClient(ssh.get_transport()) as scp: scp.get(source, destination) elif proto == 'sftp': diff -Nru ansible-2.5.3/lib/ansible/plugins/cliconf/ios.py ansible-2.5.4/lib/ansible/plugins/cliconf/ios.py --- ansible-2.5.3/lib/ansible/plugins/cliconf/ios.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/cliconf/ios.py 2018-05-31 22:01:21.000000000 +0000 @@ -72,6 +72,7 @@ @enable_mode def edit_config(self, command): + results = [] for cmd in chain(['configure terminal'], to_list(command), ['end']): if isinstance(cmd, dict): command = cmd['command'] @@ -84,7 +85,8 @@ answer = None newline = True - self.send_command(command, prompt, answer, False, newline) + results.append(self.send_command(command, prompt, answer, False, newline)) + return results[1:-1] def get(self, command, prompt=None, answer=None, sendonly=False): return self.send_command(command, prompt=prompt, answer=answer, sendonly=sendonly) diff -Nru ansible-2.5.3/lib/ansible/plugins/connection/winrm.py ansible-2.5.4/lib/ansible/plugins/connection/winrm.py --- ansible-2.5.3/lib/ansible/plugins/connection/winrm.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/connection/winrm.py 2018-05-31 22:01:21.000000000 +0000 @@ -100,7 +100,6 @@ import inspect import os import re -import shlex import traceback import json import tempfile @@ -188,8 +187,8 @@ super(Connection, self).set_options(task_keys=None, var_options=var_options, direct=direct) - self._winrm_host = self._play_context.remote_addr - self._winrm_user = self._play_context.remote_user + self._winrm_host = self.get_option('remote_addr') + self._winrm_user = self.get_option('remote_user') self._winrm_pass = self._play_context.password self._become_method = self._play_context.become_method @@ -235,7 +234,7 @@ kinit_mode = self.get_option('kerberos_mode') if kinit_mode is None: # HACK: ideally, remove multi-transport stuff - self._kerb_managed = "kerberos" in self._winrm_transport and self._winrm_pass + self._kerb_managed = "kerberos" in self._winrm_transport and (self._winrm_pass is not None and self._winrm_pass != "") elif kinit_mode == "managed": self._kerb_managed = True elif kinit_mode == "manual": @@ -286,33 +285,60 @@ # doing so. Unfortunately it is not available on the built in Python # so we can only use it if someone has installed it if HAS_PEXPECT: - kinit_cmdline = " ".join(kinit_cmdline) + proc_mechanism = "pexpect" + command = kinit_cmdline.pop(0) password = to_text(password, encoding='utf-8', errors='surrogate_or_strict') display.vvvv("calling kinit with pexpect for principal %s" % principal) - events = { - ".*:": password + "\n" - } - # technically this is the stdout but to match subprocess we will - # call it stderr - stderr, rc = pexpect.run(kinit_cmdline, withexitstatus=True, events=events, env=krb5env, timeout=60) + try: + child = pexpect.spawn(command, kinit_cmdline, timeout=60, + env=krb5env) + except pexpect.ExceptionPexpect as err: + err_msg = "Kerberos auth failure when calling kinit cmd " \ + "'%s': %s" % (command, to_native(err)) + raise AnsibleConnectionFailure(err_msg) + + try: + child.expect(".*:") + child.sendline(password) + except OSError as err: + # child exited before the pass was sent, Ansible will raise + # error based on the rc below, just display the error here + display.vvvv("kinit with pexpect raised OSError: %s" + % to_native(err)) + + # technically this is the stdout + stderr but to match the + # subprocess error checking behaviour, we will call it stderr + stderr = child.read() + child.wait() + rc = child.exitstatus else: + proc_mechanism = "subprocess" password = to_bytes(password, encoding='utf-8', errors='surrogate_or_strict') display.vvvv("calling kinit with subprocess for principal %s" % principal) - p = subprocess.Popen(kinit_cmdline, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=krb5env) + try: + p = subprocess.Popen(kinit_cmdline, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=krb5env) + + except OSError as err: + err_msg = "Kerberos auth failure when calling kinit cmd " \ + "'%s': %s" % (self._kinit_cmd, to_native(err)) + raise AnsibleConnectionFailure(err_msg) + stdout, stderr = p.communicate(password + b'\n') rc = p.returncode != 0 if rc != 0: - raise AnsibleConnectionFailure("Kerberos auth failure: %s" % to_native(stderr.strip())) + err_msg = "Kerberos auth failure for principal %s with %s: %s" \ + % (principal, proc_mechanism, to_native(stderr.strip())) + raise AnsibleConnectionFailure(err_msg) display.vvvvv("kinit succeeded for principal %s" % principal) @@ -495,47 +521,6 @@ result.std_out = to_bytes(result.std_out) result.std_err = to_bytes(result.std_err) - # parse just stderr from CLIXML output - if self.is_clixml(result.std_err): - try: - result.std_err = self.parse_clixml_stream(result.std_err) - except Exception: - # unsure if we're guaranteed a valid xml doc- use raw output in case of error - pass - - return (result.status_code, result.std_out, result.std_err) - - def exec_command_old(self, cmd, in_data=None, sudoable=True): - super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) - cmd_parts = shlex.split(to_bytes(cmd), posix=False) - cmd_parts = map(to_text, cmd_parts) - script = None - cmd_ext = cmd_parts and self._shell._unquote(cmd_parts[0]).lower()[-4:] or '' - # Support running .ps1 files (via script/raw). - if cmd_ext == '.ps1': - script = '& %s' % cmd - # Support running .bat/.cmd files; change back to the default system encoding instead of UTF-8. - elif cmd_ext in ('.bat', '.cmd'): - script = '[System.Console]::OutputEncoding = [System.Text.Encoding]::Default; & %s' % cmd - # Encode the command if not already encoded; supports running simple PowerShell commands via raw. - elif '-EncodedCommand' not in cmd_parts: - script = cmd - if script: - cmd_parts = self._shell._encode_script(script, as_list=True, strict_mode=False) - if '-EncodedCommand' in cmd_parts: - encoded_cmd = cmd_parts[cmd_parts.index('-EncodedCommand') + 1] - decoded_cmd = to_text(base64.b64decode(encoded_cmd).decode('utf-16-le')) - display.vvv("EXEC %s" % decoded_cmd, host=self._winrm_host) - else: - display.vvv("EXEC %s" % cmd, host=self._winrm_host) - try: - result = self._winrm_exec(cmd_parts[0], cmd_parts[1:], from_exec=True) - except Exception: - traceback.print_exc() - raise AnsibleConnectionFailure("failed to exec cmd %s" % to_native(cmd)) - result.std_out = to_bytes(result.std_out) - result.std_err = to_bytes(result.std_err) - # parse just stderr from CLIXML output if self.is_clixml(result.std_err): try: diff -Nru ansible-2.5.3/lib/ansible/plugins/inventory/constructed.py ansible-2.5.4/lib/ansible/plugins/inventory/constructed.py --- ansible-2.5.3/lib/ansible/plugins/inventory/constructed.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/inventory/constructed.py 2018-05-31 22:01:21.000000000 +0000 @@ -57,6 +57,7 @@ from ansible import constants as C from ansible.errors import AnsibleParserError +from ansible.inventory.helpers import get_group_vars from ansible.plugins.cache import FactCache from ansible.plugins.inventory import BaseInventoryPlugin, Constructable from ansible.module_utils._text import to_native @@ -99,7 +100,7 @@ for host in inventory.hosts: # get available variables to templar - hostvars = inventory.hosts[host].get_vars() + hostvars = combine_vars(get_group_vars(inventory.hosts[host].get_groups()), inventory.hosts[host].get_vars()) if host in fact_cache: # adds facts if cache is active hostvars = combine_vars(hostvars, fact_cache[host]) @@ -107,7 +108,7 @@ self._set_composite_vars(self.get_option('compose'), hostvars, host, strict=strict) # refetch host vars in case new ones have been created above - hostvars = inventory.hosts[host].get_vars() + hostvars = combine_vars(get_group_vars(inventory.hosts[host].get_groups()), inventory.hosts[host].get_vars()) if host in self._cache: # adds facts if cache is active hostvars = combine_vars(hostvars, self._cache[host]) diff -Nru ansible-2.5.3/lib/ansible/plugins/lookup/fileglob.py ansible-2.5.4/lib/ansible/plugins/lookup/fileglob.py --- ansible-2.5.3/lib/ansible/plugins/lookup/fileglob.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/lookup/fileglob.py 2018-05-31 22:01:21.000000000 +0000 @@ -17,7 +17,8 @@ description: path(s) of files to read required: True notes: - - Patterns ore only supported on files, not directory/paths. + - Patterns are only supported on files, not directory/paths. + - Matching is against local system files. """ EXAMPLES = """ diff -Nru ansible-2.5.3/lib/ansible/plugins/strategy/linear.py ansible-2.5.4/lib/ansible/plugins/strategy/linear.py --- ansible-2.5.3/lib/ansible/plugins/strategy/linear.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/strategy/linear.py 2018-05-31 22:01:21.000000000 +0000 @@ -263,7 +263,7 @@ # for the linear strategy, we run meta tasks just once and for # all hosts currently being iterated over rather than one host results.extend(self._execute_meta(task, play_context, iterator, host)) - if task.args.get('_raw_params', None) != 'noop': + if task.args.get('_raw_params', None) not in ('noop', 'reset_connection'): run_once = True else: # handle step if needed, skip meta actions as they are used internally diff -Nru ansible-2.5.3/lib/ansible/plugins/terminal/edgeos.py ansible-2.5.4/lib/ansible/plugins/terminal/edgeos.py --- ansible-2.5.3/lib/ansible/plugins/terminal/edgeos.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/terminal/edgeos.py 2018-05-31 22:01:21.000000000 +0000 @@ -1,6 +1,5 @@ # Copyright: (c) 2018, Ansible Project -# GNU General Public License v3.0+ -# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type diff -Nru ansible-2.5.3/lib/ansible/plugins/terminal/nxos.py ansible-2.5.4/lib/ansible/plugins/terminal/nxos.py --- ansible-2.5.3/lib/ansible/plugins/terminal/nxos.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/plugins/terminal/nxos.py 2018-05-31 22:01:21.000000000 +0000 @@ -30,8 +30,8 @@ class TerminalModule(TerminalBase): terminal_stdout_re = [ - re.compile(br'[\r\n]?(?!\s*<)?(\x1b\S+)*[a-zA-Z_]{1}[a-zA-Z0-9-_.]*[>|#|%](?:\s*)*(\x1b\S+)*$'), - re.compile(br'[\r\n]?[a-zA-Z]{1}[a-zA-Z0-9-_.]*\(.+\)#(?:\s*)$') + re.compile(br'[\r\n]?(?!\s*<)?(\x1b\S+)*[a-zA-Z_0-9]{1}[a-zA-Z0-9-_.]*[>|#|%](?:\s*)*(\x1b\S+)*$'), + re.compile(br'[\r\n]?[a-zA-Z0-9]{1}[a-zA-Z0-9-_.]*\(.+\)#(?:\s*)$') ] terminal_stderr_re = [ diff -Nru ansible-2.5.3/lib/ansible/release.py ansible-2.5.4/lib/ansible/release.py --- ansible-2.5.3/lib/ansible/release.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/lib/ansible/release.py 2018-05-31 22:01:21.000000000 +0000 @@ -19,5 +19,5 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -__version__ = '2.5.3' +__version__ = '2.5.4' __author__ = 'Ansible, Inc.' diff -Nru ansible-2.5.3/PKG-INFO ansible-2.5.4/PKG-INFO --- ansible-2.5.3/PKG-INFO 2018-05-17 23:52:29.000000000 +0000 +++ ansible-2.5.4/PKG-INFO 2018-05-31 22:02:03.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ansible -Version: 2.5.3 +Version: 2.5.4 Summary: Radically simple IT automation Home-page: https://ansible.com/ Author: Ansible, Inc. diff -Nru ansible-2.5.3/test/integration/targets/aws_ses_identity/aliases ansible-2.5.4/test/integration/targets/aws_ses_identity/aliases --- ansible-2.5.3/test/integration/targets/aws_ses_identity/aliases 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/aws_ses_identity/aliases 2018-05-31 22:01:21.000000000 +0000 @@ -1,3 +1,2 @@ cloud/aws posix/ci/cloud/group4/aws -unstable diff -Nru ansible-2.5.3/test/integration/targets/aws_ses_identity/tasks/main.yaml ansible-2.5.4/test/integration/targets/aws_ses_identity/tasks/main.yaml --- ansible-2.5.3/test/integration/targets/aws_ses_identity/tasks/main.yaml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/aws_ses_identity/tasks/main.yaml 2018-05-31 22:01:21.000000000 +0000 @@ -1,15 +1,21 @@ --- # ============================================================ +- name: set up aws connection info + set_fact: + aws_connection_info: &aws_connection_info + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token }}" + region: "{{ aws_region }}" + no_log: yes + - name: test register email identity block: - name: register email identity aws_ses_identity: identity: "{{ email_identity }}" state: present - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: result - name: assert changed is True assert: @@ -23,10 +29,7 @@ aws_ses_identity: identity: "{{ email_identity }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info # ============================================================ - name: test register domain identity block: @@ -34,10 +37,7 @@ aws_ses_identity: identity: "{{ domain_identity }}" state: present - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: result - name: assert changed is True assert: @@ -55,10 +55,7 @@ aws_ses_identity: identity: "{{ domain_identity }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info # ============================================================ - name: test email_identity unchanged when already existing block: @@ -66,18 +63,12 @@ aws_ses_identity: identity: "{{ email_identity }}" state: present - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info - name: duplicate register identity aws_ses_identity: identity: "{{ email_identity }}" state: present - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: result - name: assert changed is False assert: @@ -91,10 +82,7 @@ aws_ses_identity: identity: "{{ email_identity }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info # ============================================================ - name: test domain_identity unchanged when already existing block: @@ -102,18 +90,12 @@ aws_ses_identity: identity: "{{ domain_identity }}" state: present - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info - name: duplicate register identity aws_ses_identity: identity: "{{ domain_identity }}" state: present - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: result - name: assert changed is False assert: @@ -127,19 +109,13 @@ aws_ses_identity: identity: "{{ domain_identity }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info # ============================================================ - name: remove non-existent email identity aws_ses_identity: identity: "{{ email_identity }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: result - name: assert changed is False assert: @@ -150,10 +126,7 @@ aws_ses_identity: identity: "{{ domain_identity }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: result - name: assert changed is False assert: @@ -166,10 +139,7 @@ sns_topic: name: "{{ notification_queue_name }}-{{ item }}" state: present - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: topic_info with_items: - bounce @@ -185,10 +155,7 @@ topic: "{{ topic_info.results[1].sns_arn }}" delivery_notifications: topic: "{{ topic_info.results[2].sns_arn }}" - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: result - name: assert notification settings assert: @@ -207,10 +174,7 @@ sns_topic: name: "{{ notification_queue_name }}-{{ item }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info with_items: - bounce - complaint @@ -219,10 +183,7 @@ aws_ses_identity: identity: "{{ email_identity }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info # ============================================================ - name: test change notification queues after create block: @@ -230,10 +191,7 @@ sns_topic: name: "{{ notification_queue_name }}-{{ item }}" state: present - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: topic_info with_items: - bounce @@ -243,10 +201,7 @@ aws_ses_identity: identity: "{{ email_identity }}" state: present - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info - name: set notification topics aws_ses_identity: identity: "{{ email_identity }}" @@ -257,10 +212,7 @@ topic: "{{ topic_info.results[1].sns_arn }}" delivery_notifications: topic: "{{ topic_info.results[2].sns_arn }}" - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: result - name: assert changed is True assert: @@ -277,10 +229,7 @@ sns_topic: name: "{{ notification_queue_name }}-{{ item }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info with_items: - bounce - complaint @@ -289,10 +238,7 @@ aws_ses_identity: identity: "{{ email_identity }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info # ============================================================ - name: test include headers on notification queues block: @@ -306,10 +252,7 @@ include_headers: Yes delivery_notifications: include_headers: Yes - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: result - name: assert notification headers enabled assert: @@ -322,10 +265,7 @@ aws_ses_identity: identity: "{{ email_identity }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info # ============================================================ - name: test disable feedback forwarding block: @@ -333,10 +273,7 @@ sns_topic: name: "{{ notification_queue_name }}-{{ item }}" state: present - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: topic_info with_items: - bounce @@ -350,10 +287,7 @@ complaint_notifications: topic: "{{ topic_info.results[1].sns_arn }}" feedback_forwarding: No - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: result - name: assert feedback_forwarding == False assert: @@ -364,10 +298,7 @@ sns_topic: name: "{{ notification_queue_name }}-{{ item }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info with_items: - bounce - complaint @@ -375,10 +306,7 @@ aws_ses_identity: identity: "{{ email_identity }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info # ============================================================ - name: test disable feedback forwarding fails if no topics block: @@ -387,22 +315,84 @@ identity: "{{ domain_identity }}" state: present feedback_forwarding: No - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info register: result failed_when: result.failed == False - - name: assert error.code == InvalidParameterValue + - name: assert error message starts with "Invalid Parameter Value" assert: that: - - result.error.code == 'InvalidParameterValue' + - '"Invalid Parameter Value" in result.msg' always: - name: cleanup identity aws_ses_identity: identity: "{{ domain_identity }}" state: absent - region: "{{ ec2_region }}" - aws_access_key: "{{ ec2_access_key }}" - aws_secret_key: "{{ ec2_secret_key }}" - security_token: "{{security_token}}" + <<: *aws_connection_info +# ============================================================ +- name: test disable feedback forwarding fails if no complaint topic + block: + - name: test topic + sns_topic: + name: "{{ notification_queue_name }}-bounce" + state: present + <<: *aws_connection_info + register: topic_info + - name: register email identity + aws_ses_identity: + identity: "{{ email_identity }}" + state: present + bounce_notifications: + topic: "{{ topic_info.sns_arn }}" + feedback_forwarding: No + <<: *aws_connection_info + register: result + failed_when: result.failed == False + - name: assert error message starts with "Invalid Parameter Value" + assert: + that: + - '"Invalid Parameter Value" in result.msg' + always: + - name: cleanup topics + sns_topic: + name: "{{ notification_queue_name }}-bounce" + state: absent + <<: *aws_connection_info + - name: cleanup identity + aws_ses_identity: + identity: "{{ email_identity }}" + state: absent + <<: *aws_connection_info +# ============================================================ +- name: test disable feedback forwarding fails if no bounce topic + block: + - name: test topic + sns_topic: + name: "{{ notification_queue_name }}-complaint" + state: present + <<: *aws_connection_info + register: topic_info + - name: register email identity + aws_ses_identity: + identity: "{{ email_identity }}" + state: present + complaint_notifications: + topic: "{{ topic_info.sns_arn }}" + feedback_forwarding: No + <<: *aws_connection_info + register: result + failed_when: result.failed == False + - name: assert error message starts with "Invalid Parameter Value" + assert: + that: + - '"Invalid Parameter Value" in result.msg' + always: + - name: cleanup topics + sns_topic: + name: "{{ notification_queue_name }}-complaint" + state: absent + <<: *aws_connection_info + - name: cleanup identity + aws_ses_identity: + identity: "{{ email_identity }}" + state: absent + <<: *aws_connection_info diff -Nru ansible-2.5.3/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml ansible-2.5.4/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml --- ansible-2.5.3/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml 2018-05-31 22:01:21.000000000 +0000 @@ -1,9 +1,14 @@ -- name: Delete virtual machine +- name: Delete virtual machines azure_rm_virtualmachine: resource_group: "{{ resource_group }}" - name: testvm002 + name: "{{ vms }}" state: absent vm_size: Standard_A0 + loop: + - testvm002 + - testvm003 + loop_control: + loop_var: vms register: output - name: Create storage account name @@ -59,7 +64,7 @@ priority: 110 direction: Inbound -- name: Create NIC +- name: Create NIC for single nic VM azure_rm_networkinterface: resource_group: "{{ resource_group }}" name: testvm001 @@ -68,7 +73,7 @@ public_ip_name: testvm001 security_group: testvm001 -- name: Create virtual machine +- name: Create virtual machine with a single NIC register: output azure_rm_virtualmachine: resource_group: "{{ resource_group }}" @@ -173,7 +178,7 @@ - "azure_vm.powerstate in ['starting', 'running']" - output.changed -- name: Should be idempotent +- name: Should be idempotent with a single NIC azure_rm_virtualmachine: resource_group: "{{ resource_group }}" name: testvm002 diff -Nru ansible-2.5.3/test/integration/targets/git/tasks/main.yml ansible-2.5.4/test/integration/targets/git/tasks/main.yml --- ansible-2.5.3/test/integration/targets/git/tasks/main.yml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/git/tasks/main.yml 2018-05-31 22:01:21.000000000 +0000 @@ -36,3 +36,6 @@ - include_tasks: reset-origin.yml - include_tasks: ambiguous-ref.yml - include_tasks: archive.yml +- include_tasks: separate-git-dir.yml + when: + - git_version.stdout is version("1.7.5", '>=') diff -Nru ansible-2.5.3/test/integration/targets/git/tasks/separate-git-dir.yml ansible-2.5.4/test/integration/targets/git/tasks/separate-git-dir.yml --- ansible-2.5.3/test/integration/targets/git/tasks/separate-git-dir.yml 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/git/tasks/separate-git-dir.yml 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1,64 @@ +# test code for repositories with separate git dir updating +# see https://github.com/ansible/ansible/pull/38016 +# see https://github.com/ansible/ansible/issues/30034 + +- name: SEPARATE-GIT-DIR | clear checkout_dir + file: + state: absent + path: '{{ checkout_dir }}' + +- name: create a tempdir for separate git dir + local_action: shell mktemp -du + register: tempdir + +- name: SEPARATE-GIT-DIR | clone with a separate git dir + command: git clone {{ repo_format1 }} {{ checkout_dir }} --separate-git-dir={{ tempdir.stdout }} + +- name: SEPARATE-GIT-DIR | update repo the usual way + git: + repo: "{{ repo_format1 }}" + dest: "{{ checkout_dir }}" + +- name: SEPARATE-GIT-DIR | set git dir to non-existent dir + shell: "echo gitdir: /dev/null/non-existent-dir > .git" + args: + chdir: "{{ checkout_dir }}" + +- name: SEPARATE-GIT-DIR | update repo the usual way + git: + repo: "{{ repo_format1 }}" + dest: "{{ checkout_dir }}" + ignore_errors: yes + register: result + +- name: SEPARATE-GIT-DIR | check update has failed + assert: + that: + - result is failed + +- name: SEPARATE-GIT-DIR | set .git file to bad format + shell: "echo some text gitdir: {{ checkout_dir }} > .git" + args: + chdir: "{{ checkout_dir }}" + +- name: SEPARATE-GIT-DIR | update repo the usual way + git: + repo: "{{ repo_format1 }}" + dest: "{{ checkout_dir }}" + ignore_errors: yes + register: result + +- name: SEPARATE-GIT-DIR | check update has failed + assert: + that: + - result is failed + +- name: SEPARATE-GIT-DIR | clear separate git dir + file: + state: absent + path: "{{ tempdir.stdout }}" + +- name: SEPARATE-GIT-DIR | clear checkout_dir + file: + state: absent + path: '{{ checkout_dir }}' diff -Nru ansible-2.5.3/test/integration/targets/ios_l3_interface/tests/cli/basic.yaml ansible-2.5.4/test/integration/targets/ios_l3_interface/tests/cli/basic.yaml --- ansible-2.5.3/test/integration/targets/ios_l3_interface/tests/cli/basic.yaml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/ios_l3_interface/tests/cli/basic.yaml 2018-05-31 22:01:21.000000000 +0000 @@ -22,7 +22,7 @@ - name: Configure interface ipv4 address ios_l3_interface: name: "{{ test_interface }}" - ipv4: 192.168.0.1/24 + ipv4: 192.168.20.1/24 state: present provider: "{{ cli }}" register: result @@ -31,24 +31,24 @@ that: - 'result.changed == true' - '"interface {{ test_interface }}" in result.commands' - - '"ip address 192.168.0.1 255.255.255.0" in result.commands' + - '"ip address 192.168.20.1 255.255.255.0" in result.commands' - name: Configure interface ipv4 address (idempotent) ios_l3_interface: name: "{{ test_interface }}" - ipv4: 192.168.0.1/24 + ipv4: 192.168.20.1/24 state: present provider: "{{ cli }}" register: result -- assert: +- assert: &unchanged that: - 'result.changed == false' - name: Assign same ipv4 address to other interface (fail) ios_l3_interface: name: "{{ test_interface2 }}" - ipv4: 192.168.0.1/24 + ipv4: 192.168.20.1/24 state: present provider: "{{ cli }}" ignore_errors: yes @@ -74,7 +74,7 @@ - '"ip address dhcp" in result.commands' - name: Configure interface ipv6 address - ios_l3_interface: + ios_l3_interface: &ipv6-1 name: "{{ test_interface }}" ipv6: fd5d:12c9:2201:1::1/64 state: present @@ -88,16 +88,36 @@ - '"ipv6 address fd5d:12c9:2201:1::1/64" in result.commands' - name: Configure interface ipv6 address (idempotent) - ios_l3_interface: + ios_l3_interface: *ipv6-1 + register: result + +- assert: *unchanged + +- name: Configure second ipv6 address on interface + ios_l3_interface: &ipv6-2 name: "{{ test_interface }}" - ipv6: fd5d:12c9:2201:1::1/64 + ipv6: fd5d:12c9:2291:1::1/64 state: present provider: "{{ cli }}" register: result - assert: that: - - 'result.changed == false' + - 'result.changed == true' + - '"interface {{ test_interface }}" in result.commands' + - '"ipv6 address fd5d:12c9:2291:1::1/64" in result.commands' + +- name: Ensure first ipv6 address still associated with interface + ios_l3_interface: *ipv6-1 + register: result + +- assert: *unchanged + +- name: Ensure second ipv6 address still associated with interface + ios_l3_interface: *ipv6-2 + register: result + +- assert: *unchanged - name: Assign same ipv6 address to other interface (fail) ios_l3_interface: @@ -148,6 +168,8 @@ provider: "{{ cli }}" register: result +- assert: *unchanged + - name: Delete second interface ipv4 and ipv6 address (setup) ios_l3_interface: name: "{{ test_interface2 }}" @@ -181,9 +203,7 @@ provider: "{{ cli }}" register: result -- assert: - that: - - 'result.changed == false' +- assert: *unchanged - name: Change ipv4 and ipv6 address using aggregate ios_l3_interface: @@ -232,8 +252,6 @@ provider: "{{ cli }}" register: result -- assert: - that: - - 'result.changed == false' +- assert: *unchanged - debug: msg="END ios_l3_interface cli/basic.yaml on connection={{ ansible_connection }}" diff -Nru ansible-2.5.3/test/integration/targets/known_hosts/tasks/main.yml ansible-2.5.4/test/integration/targets/known_hosts/tasks/main.yml --- ansible-2.5.3/test/integration/targets/known_hosts/tasks/main.yml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/known_hosts/tasks/main.yml 2018-05-31 22:01:21.000000000 +0000 @@ -17,7 +17,9 @@ # along with Ansible. If not, see . - name: copy an existing file in place - copy: src=existing_known_hosts dest="{{output_dir|expanduser}}/known_hosts" + copy: + src: existing_known_hosts + dest: "{{ output_dir | expanduser }}/known_hosts" # test addition @@ -167,3 +169,33 @@ - 'not result.changed' - 'result.diff.before == result.diff.after' - 'known_hosts_v3.stdout == known_hosts_v4.stdout' + +# test errors + +- name: Try using a comma separated list of hosts + known_hosts: + name: example.org,acme.com + key: "{{ example_org_rsa_key }}" + path: "{{output_dir|expanduser}}/known_hosts" + ignore_errors: yes + register: result + +- name: Assert that error message was displayed + assert: + that: + - result is failed + - result.msg == 'Comma separated list of names is not supported. Please pass a single name to lookup in the known_hosts file.' + +- name: Try using a name that does not match the key + known_hosts: + name: example.com + key: "{{ example_org_rsa_key }}" + path: "{{output_dir|expanduser}}/known_hosts" + ignore_errors: yes + register: result + +- name: Assert that name checking failed with error message + assert: + that: + - result is failed + - result.msg == 'Host parameter does not match hashed host field in supplied key' diff -Nru ansible-2.5.3/test/integration/targets/nxos_config/tests/common/sublevel_block.yaml ansible-2.5.4/test/integration/targets/nxos_config/tests/common/sublevel_block.yaml --- ansible-2.5.3/test/integration/targets/nxos_config/tests/common/sublevel_block.yaml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/nxos_config/tests/common/sublevel_block.yaml 2018-05-31 22:01:21.000000000 +0000 @@ -8,6 +8,7 @@ lines: no ip access-list test provider: "{{ connection }}" match: none + ignore_errors: yes - name: configure sub level command using block replace nxos_config: diff -Nru ansible-2.5.3/test/integration/targets/nxos_evpn_global/tests/common/sanity.yaml ansible-2.5.4/test/integration/targets/nxos_evpn_global/tests/common/sanity.yaml --- ansible-2.5.3/test/integration/targets/nxos_evpn_global/tests/common/sanity.yaml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/nxos_evpn_global/tests/common/sanity.yaml 2018-05-31 22:01:21.000000000 +0000 @@ -57,7 +57,7 @@ - assert: *false - when: not ( platform is search('N3K')) + when: not ( platform is search('N3K|N35|N3L')) rescue: - debug: msg="connection={{ ansible_connection }} nxos_evpn_global sanity test - FALURE ENCOUNTERED" diff -Nru ansible-2.5.3/test/integration/targets/nxos_pim_rp_address/tests/common/configure.yaml ansible-2.5.4/test/integration/targets/nxos_pim_rp_address/tests/common/configure.yaml --- ansible-2.5.3/test/integration/targets/nxos_pim_rp_address/tests/common/configure.yaml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/nxos_pim_rp_address/tests/common/configure.yaml 2018-05-31 22:01:21.000000000 +0000 @@ -3,6 +3,10 @@ - debug: msg="Using provider={{ connection.transport }}" when: ansible_connection == "local" +- set_fact: bidir="true" +- set_fact: bidir="false" + when: platform is match("N3L") + - block: - name: "Disable feature PIM" nxos_feature: &disable_feature @@ -20,7 +24,7 @@ nxos_pim_rp_address: &configgl rp_address: "10.1.1.20" group_list: "224.0.0.0/8" - bidir: True + bidir: "{{ bidir }}" state: present provider: "{{ connection }}" register: result @@ -45,19 +49,23 @@ state: present provider: "{{ connection }}" register: result + when: platform is not match("N3L") - assert: *true + when: platform is not match("N3L") - name: Check idempotence rp_address + group_list remove bidir nxos_pim_rp_address: *configglnb register: result + when: platform is not match("N3L") - assert: *false + when: platform is not match("N3L") - name: Configure rp_address + bidir nxos_pim_rp_address: &configbi rp_address: "10.1.1.20" - bidir: True + bidir: "{{ bidir }}" state: present provider: "{{ connection }}" register: result @@ -77,14 +85,18 @@ state: present provider: "{{ connection }}" register: result + when: platform is not match("N3L") - assert: *true + when: platform is not match("N3L") - name: Check idempotence rp_address remove bidir nxos_pim_rp_address: *confignbi register: result + when: platform is not match("N3L") - assert: *false + when: platform is not match("N3L") - name: Remove rp_address + group_list nxos_pim_rp_address: &configglr @@ -121,7 +133,7 @@ nxos_pim_rp_address: &configpl rp_address: "10.1.1.20" prefix_list: "pim_prefix_list" - bidir: True + bidir: "{{ bidir }}" state: present provider: "{{ connection }}" register: result @@ -142,14 +154,18 @@ state: present provider: "{{ connection }}" register: result + when: platform is not match("N3L") - assert: *true + when: platform is not match("N3L") - name: Check idempotence rp_address + prefix_list nxos_pim_rp_address: *configplnbi register: result + when: platform is not match("N3L") - assert: *false + when: platform is not match("N3L") - name: Remove rp_address + prefix_list nxos_pim_rp_address: &configplr @@ -172,7 +188,7 @@ nxos_pim_rp_address: &configrm rp_address: "10.1.1.20" route_map: "pim_routemap" - bidir: True + bidir: "{{ bidir }}" state: present provider: "{{ connection }}" register: result @@ -193,14 +209,18 @@ state: present provider: "{{ connection }}" register: result + when: platform is not match("N3L") - assert: *true + when: platform is not match("N3L") - name: Check idempotence rp_address + route_map nxos_pim_rp_address: *configrmnbi register: result + when: platform is not match("N3L") - assert: *false + when: platform is not match("N3L") - name: Remove rp_address + route_map nxos_pim_rp_address: &configrmr diff -Nru ansible-2.5.3/test/integration/targets/nxos_vrf/tests/common/sanity.yaml ansible-2.5.4/test/integration/targets/nxos_vrf/tests/common/sanity.yaml --- ansible-2.5.3/test/integration/targets/nxos_vrf/tests/common/sanity.yaml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/nxos_vrf/tests/common/sanity.yaml 2018-05-31 22:01:21.000000000 +0000 @@ -13,10 +13,10 @@ when: (platform is not match("N35|N7K")) and ((imagetag != 'I2')) - set_fact: vnind="5000" - when: platform is not match("N35|N7K") + when: platform is not match("N35|N7K|N3L") - set_fact: vnid="default" - when: platform is not match("N35|N7K") + when: platform is not match("N35|N7K|N3L") - name: "Enable feature BGP" nxos_feature: diff -Nru ansible-2.5.3/test/integration/targets/nxos_vrf_af/tests/common/sanity.yaml ansible-2.5.4/test/integration/targets/nxos_vrf_af/tests/common/sanity.yaml --- ansible-2.5.3/test/integration/targets/nxos_vrf_af/tests/common/sanity.yaml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/nxos_vrf_af/tests/common/sanity.yaml 2018-05-31 22:01:21.000000000 +0000 @@ -130,7 +130,7 @@ - assert: *false - when: not platform is search("N35") + when: not platform is search("N35|N3L") always: - name: Remove vrf diff -Nru ansible-2.5.3/test/integration/targets/pause/aliases ansible-2.5.4/test/integration/targets/pause/aliases --- ansible-2.5.3/test/integration/targets/pause/aliases 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/pause/aliases 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1 @@ +posix/ci/group2 diff -Nru ansible-2.5.3/test/integration/targets/pause/pause-1.yml ansible-2.5.4/test/integration/targets/pause/pause-1.yml --- ansible-2.5.3/test/integration/targets/pause/pause-1.yml 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/pause/pause-1.yml 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1,11 @@ +- name: Test pause module in default state + hosts: testhost + become: no + gather_facts: no + + tasks: + - name: EXPECTED FAILURE + pause: + + - debug: + msg: Task after pause diff -Nru ansible-2.5.3/test/integration/targets/pause/pause-2.yml ansible-2.5.4/test/integration/targets/pause/pause-2.yml --- ansible-2.5.3/test/integration/targets/pause/pause-2.yml 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/pause/pause-2.yml 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1,12 @@ +- name: Test pause module with custom prompt + hosts: testhost + become: no + gather_facts: no + + tasks: + - name: EXPECTED FAILURE + pause: + prompt: Custom prompt + + - debug: + msg: Task after pause diff -Nru ansible-2.5.3/test/integration/targets/pause/pause-3.yml ansible-2.5.4/test/integration/targets/pause/pause-3.yml --- ansible-2.5.3/test/integration/targets/pause/pause-3.yml 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/pause/pause-3.yml 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1,12 @@ +- name: Test pause module with pause + hosts: testhost + become: no + gather_facts: no + + tasks: + - name: EXPECTED FAILURE + pause: + seconds: 2 + + - debug: + msg: Task after pause diff -Nru ansible-2.5.3/test/integration/targets/pause/pause-4.yml ansible-2.5.4/test/integration/targets/pause/pause-4.yml --- ansible-2.5.3/test/integration/targets/pause/pause-4.yml 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/pause/pause-4.yml 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1,13 @@ +- name: Test pause module with pause and custom prompt + hosts: testhost + become: no + gather_facts: no + + tasks: + - name: EXPECTED FAILURE + pause: + seconds: 2 + prompt: Waiting for two seconds + + - debug: + msg: Task after pause diff -Nru ansible-2.5.3/test/integration/targets/pause/pause-5.yml ansible-2.5.4/test/integration/targets/pause/pause-5.yml --- ansible-2.5.3/test/integration/targets/pause/pause-5.yml 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/pause/pause-5.yml 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1,35 @@ +- name: Test pause module echo output + hosts: testhost + become: no + gather_facts: no + + tasks: + - pause: + echo: yes + prompt: Enter some text + register: results + + - name: Ensure that input was captured + assert: + that: + - results.user_input == 'hello there' + + - pause: + echo: yes + prompt: Enter some text to edit + register: result + + - name: Ensure edited input was captured + assert: + that: + - result.user_input == 'hello tommy boy' + + - pause: + echo: no + prompt: Enter some text + register: result + + - name: Ensure secret input was caputered + assert: + that: + - result.user_input == 'supersecretpancakes' diff -Nru ansible-2.5.3/test/integration/targets/pause/runme.sh ansible-2.5.4/test/integration/targets/pause/runme.sh --- ansible-2.5.3/test/integration/targets/pause/runme.sh 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/pause/runme.sh 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -eux + +# Test pause module when no tty and non-interactive. This is to prevent playbooks +# from hanging in cron and Tower jobs. +/usr/bin/env bash << EOF +ansible-playbook test-pause-no-tty.yml -i ../../inventory 2>&1 | \ + grep '\[WARNING\]: Not waiting for response to prompt as stdin is not interactive' && { + echo 'Successfully skipped pause in no TTY mode' >&2 + exit 0 + } || { + echo 'Failed to skip pause module' >&2 + exit 1 + } +EOF + +# Test pause with seconds and minutes specified +ansible-playbook test-pause.yml -i ../../inventory "$@" + +# Interactively test pause +pip install pexpect +python test-pause.py -i ../../inventory "$@" diff -Nru ansible-2.5.3/test/integration/targets/pause/test-pause-no-tty.yml ansible-2.5.4/test/integration/targets/pause/test-pause-no-tty.yml --- ansible-2.5.3/test/integration/targets/pause/test-pause-no-tty.yml 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/pause/test-pause-no-tty.yml 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1,7 @@ +- name: Test pause + hosts: testhost + gather_facts: no + become: no + + tasks: + - pause: diff -Nru ansible-2.5.3/test/integration/targets/pause/test-pause.py ansible-2.5.4/test/integration/targets/pause/test-pause.py --- ansible-2.5.3/test/integration/targets/pause/test-pause.py 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/pause/test-pause.py 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1,270 @@ +#!/usr/bin/env python + +import os +import pexpect +import sys +import termios + +from ansible.module_utils.six import PY2 + +args = sys.argv[1:] + +env_vars = { + 'ANSIBLE_ROLES_PATH': './roles', + 'ANSIBLE_NOCOLOR': 'True', + 'ANSIBLE_RETRY_FILES_ENABLED': 'False' +} + +try: + backspace = termios.tcgetattr(sys.stdin.fileno())[6][termios.VERASE] +except Exception: + backspace = b'\x7f' + +if PY2: + log_buffer = sys.stdout +else: + log_buffer = sys.stdout.buffer + +os.environ.update(env_vars) + +# -- Plain pause -- # +playbook = 'pause-1.yml' + +# Case 1 - Contiune with enter +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Press enter to continue, Ctrl\+C to interrupt:') +pause_test.send('\r') +pause_test.expect('Task after pause') +pause_test.expect(pexpect.EOF) +pause_test.close() + + +# Case 2 - Continue with C +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Press enter to continue, Ctrl\+C to interrupt:') +pause_test.send('\x03') +pause_test.expect("Press 'C' to continue the play or 'A' to abort") +pause_test.send('C') +pause_test.expect('Task after pause') +pause_test.expect(pexpect.EOF) +pause_test.close() + + +# Case 3 - Abort with A +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Press enter to continue, Ctrl\+C to interrupt:') +pause_test.send('\x03') +pause_test.expect("Press 'C' to continue the play or 'A' to abort") +pause_test.send('A') +pause_test.expect('user requested abort!') +pause_test.expect(pexpect.EOF) +pause_test.close() + +# -- Custom Prompt -- # +playbook = 'pause-2.yml' + +# Case 1 - Contiune with enter +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Custom prompt:') +pause_test.send('\r') +pause_test.expect('Task after pause') +pause_test.expect(pexpect.EOF) +pause_test.close() + + +# Case 2 - Contiune with C +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Custom prompt:') +pause_test.send('\x03') +pause_test.expect("Press 'C' to continue the play or 'A' to abort") +pause_test.send('C') +pause_test.expect('Task after pause') +pause_test.expect(pexpect.EOF) +pause_test.close() + + +# Case 3 - Abort with A +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Custom prompt:') +pause_test.send('\x03') +pause_test.expect("Press 'C' to continue the play or 'A' to abort") +pause_test.send('A') +pause_test.expect('user requested abort!') +pause_test.expect(pexpect.EOF) +pause_test.close() + +# -- Pause for N seconds -- # + +playbook = 'pause-3.yml' + +# Case 1 - Wait for task to continue after timeout +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Pausing for \d+ seconds') +pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)") +pause_test.expect('Task after pause') +pause_test.expect(pexpect.EOF) +pause_test.close() + +# Case 2 - Contiune with Ctrl + C, C +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Pausing for \d+ seconds') +pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)") +pause_test.send('\x03') +pause_test.send('C') +pause_test.expect('Task after pause') +pause_test.expect(pexpect.EOF) +pause_test.close() + + +# Case 3 - Abort with Ctrl + C, A +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Pausing for \d+ seconds') +pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)") +pause_test.send('\x03') +pause_test.send('A') +pause_test.expect('user requested abort!') +pause_test.expect(pexpect.EOF) +pause_test.close() + +# -- Pause for N seconds with custom prompt -- # + +playbook = 'pause-4.yml' + +# Case 1 - Wait for task to continue after timeout +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Pausing for \d+ seconds') +pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)") +pause_test.expect(r"Waiting for two seconds:") +pause_test.expect('Task after pause') +pause_test.expect(pexpect.EOF) +pause_test.close() + +# Case 2 - Contiune with Ctrl + C, C +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Pausing for \d+ seconds') +pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)") +pause_test.expect(r"Waiting for two seconds:") +pause_test.send('\x03') +pause_test.send('C') +pause_test.expect('Task after pause') +pause_test.expect(pexpect.EOF) +pause_test.close() + + +# Case 3 - Abort with Ctrl + C, A +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Pausing for \d+ seconds') +pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)") +pause_test.expect(r"Waiting for two seconds:") +pause_test.send('\x03') +pause_test.send('A') +pause_test.expect('user requested abort!') +pause_test.expect(pexpect.EOF) +pause_test.close() + +# -- Enter input and ensure it's caputered, echoed, and can be edited -- # + +playbook = 'pause-5.yml' + +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Enter some text:') +pause_test.sendline('hello there') +pause_test.expect(r'Enter some text to edit:') +pause_test.send('hello there') +pause_test.send(backspace * 4) +pause_test.send('ommy boy\r') +pause_test.expect(r'Enter some text \(output is hidden\):') +pause_test.sendline('supersecretpancakes') +pause_test.expect(pexpect.EOF) +pause_test.close() diff -Nru ansible-2.5.3/test/integration/targets/pause/test-pause.yml ansible-2.5.4/test/integration/targets/pause/test-pause.yml --- ansible-2.5.3/test/integration/targets/pause/test-pause.yml 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/pause/test-pause.yml 2018-05-31 22:01:21.000000000 +0000 @@ -0,0 +1,21 @@ +- name: Test pause + hosts: testhost + gather_facts: no + become: no + + tasks: + - pause: + seconds: 1 + register: results + + - assert: + that: + - results.stdout is search('Paused for \d+\.\d+ seconds') + + - pause: + minutes: 1 + register: results + + - assert: + that: + - results.stdout is search('Paused for \d+\.\d+ minutes') diff -Nru ansible-2.5.3/test/integration/targets/prepare_nxos_tests/tasks/main.yml ansible-2.5.4/test/integration/targets/prepare_nxos_tests/tasks/main.yml --- ansible-2.5.3/test/integration/targets/prepare_nxos_tests/tasks/main.yml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/prepare_nxos_tests/tasks/main.yml 2018-05-31 22:01:21.000000000 +0000 @@ -80,6 +80,10 @@ - set_fact: platform="N35NG" when: ( chassis_type is search("C35")) and image_version is search("7.0\(3\)I7") +# Set platform to N3L(N3K Legacy) for C3048 platform. +- set_fact: platform="N3L" + when: ( chassis_type is search("C3048")) + # Create matrix of simple keys based on platform # and image version for use within test playbooks. - set_fact: imagetag="" diff -Nru ansible-2.5.3/test/integration/targets/win_environment/tasks/main.yml ansible-2.5.4/test/integration/targets/win_environment/tasks/main.yml --- ansible-2.5.3/test/integration/targets/win_environment/tasks/main.yml 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/integration/targets/win_environment/tasks/main.yml 2018-05-31 22:01:21.000000000 +0000 @@ -9,6 +9,23 @@ - process - user +- name: fail to create environment value with null value + win_environment: + name: "{{test_environment_name}}" + state: present + level: machine + register: create_fail_null + failed_when: create_fail_null.msg != "When state=present, value must be defined and not an empty string, if you wish to remove the envvar, set state=absent" + +- name: fail to create environment value with empty value + win_environment: + name: "{{test_environment_name}}" + value: '' + state: present + level: machine + register: create_fail_empty_string + failed_when: create_fail_null.msg != "When state=present, value must be defined and not an empty string, if you wish to remove the envvar, set state=absent" + - name: create test environment value for machine check win_environment: name: "{{test_environment_name}}" diff -Nru ansible-2.5.3/test/runner/setup/remote.sh ansible-2.5.4/test/runner/setup/remote.sh --- ansible-2.5.3/test/runner/setup/remote.sh 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/runner/setup/remote.sh 2018-05-31 22:01:22.000000000 +0000 @@ -16,6 +16,9 @@ curl \ gtar \ python \ + py27-Jinja2 \ + py27-virtualenv \ + py27-cryptography \ sudo \ && break echo "Failed to install packages. Sleeping before trying again..." diff -Nru ansible-2.5.3/test/sanity/import/lib/ansible/module_utils/network/ios/ios.py ansible-2.5.4/test/sanity/import/lib/ansible/module_utils/network/ios/ios.py --- ansible-2.5.3/test/sanity/import/lib/ansible/module_utils/network/ios/ios.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/test/sanity/import/lib/ansible/module_utils/network/ios/ios.py 2018-05-31 22:01:21.000000000 +0000 @@ -165,4 +165,4 @@ def load_config(module, commands): connection = get_connection(module) - out = connection.edit_config(commands) + return connection.edit_config(commands) diff -Nru ansible-2.5.3/test/sanity/import/lib/ansible/module_utils/network/nxos/nxos.py ansible-2.5.4/test/sanity/import/lib/ansible/module_utils/network/nxos/nxos.py --- ansible-2.5.3/test/sanity/import/lib/ansible/module_utils/network/nxos/nxos.py 2018-05-17 23:51:15.000000000 +0000 +++ ansible-2.5.4/test/sanity/import/lib/ansible/module_utils/network/nxos/nxos.py 2018-05-31 22:01:21.000000000 +0000 @@ -478,3 +478,64 @@ def get_capabilities(module): conn = get_connection(module) return conn.get_capabilities() + + +def normalize_interface(name): + """Return the normalized interface name + """ + if not name: + return + + def _get_number(name): + digits = '' + for char in name: + if char.isdigit() or char in '/.': + digits += char + return digits + + if name.lower().startswith('et'): + if_type = 'Ethernet' + elif name.lower().startswith('vl'): + if_type = 'Vlan' + elif name.lower().startswith('lo'): + if_type = 'loopback' + elif name.lower().startswith('po'): + if_type = 'port-channel' + elif name.lower().startswith('nv'): + if_type = 'nve' + else: + if_type = None + + number_list = name.split(' ') + if len(number_list) == 2: + number = number_list[-1].strip() + else: + number = _get_number(name) + + if if_type: + proper_interface = if_type + number + else: + proper_interface = name + + return proper_interface + + +def get_interface_type(interface): + """Gets the type of interface + """ + if interface.upper().startswith('ET'): + return 'ethernet' + elif interface.upper().startswith('VL'): + return 'svi' + elif interface.upper().startswith('LO'): + return 'loopback' + elif interface.upper().startswith('MG'): + return 'management' + elif interface.upper().startswith('MA'): + return 'management' + elif interface.upper().startswith('PO'): + return 'portchannel' + elif interface.upper().startswith('NV'): + return 'nve' + else: + return 'unknown' diff -Nru ansible-2.5.3/test/sanity/pylint/ignore.txt ansible-2.5.4/test/sanity/pylint/ignore.txt --- ansible-2.5.3/test/sanity/pylint/ignore.txt 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/sanity/pylint/ignore.txt 2018-05-31 22:01:22.000000000 +0000 @@ -47,7 +47,6 @@ lib/ansible/modules/network/eos/eos_logging.py ansible-format-automatic-specification lib/ansible/modules/network/eos/eos_static_route.py ansible-format-automatic-specification lib/ansible/modules/network/ios/ios_l3_interface.py ansible-format-automatic-specification -lib/ansible/modules/network/ios/ios_vlan.py ansible-format-automatic-specification lib/ansible/modules/network/iosxr/iosxr_banner.py ansible-format-automatic-specification lib/ansible/modules/network/iosxr/iosxr_interface.py ansible-format-automatic-specification lib/ansible/modules/network/nxos/nxos_logging.py ansible-format-automatic-specification diff -Nru ansible-2.5.3/test/sanity/validate-modules/schema.py ansible-2.5.4/test/sanity/validate-modules/schema.py --- ansible-2.5.3/test/sanity/validate-modules/schema.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/sanity/validate-modules/schema.py 2018-05-31 22:01:22.000000000 +0000 @@ -92,7 +92,7 @@ # Deprecation cycle changed at 2.4 (though not retroactively) # 2.3 -> removed_in: "2.5" + n for docs stub # 2.4 -> removed_in: "2.8" + n for docs stub - Required('removed_in'): Any("2.2", "2.3", "2.4", "2.5", "2.8", "2.9", "2.10"), + Required('removed_in'): Any("2.2", "2.3", "2.4", "2.5", "2.6", "2.8", "2.9", "2.10"), Required('why'): Any(*string_types), Required('alternative'): Any(*string_types), 'removed': Any(True), diff -Nru ansible-2.5.3/test/units/modules/network/ios/fixtures/ios_vlan_config.cfg ansible-2.5.4/test/units/modules/network/ios/fixtures/ios_vlan_config.cfg --- ansible-2.5.3/test/units/modules/network/ios/fixtures/ios_vlan_config.cfg 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/units/modules/network/ios/fixtures/ios_vlan_config.cfg 2018-05-31 22:01:22.000000000 +0000 @@ -0,0 +1,8 @@ +VLAN Name Status Ports +---- -------------------------------- --------- ------------------------------- +1 default active Gi1/0/4, Gi1/0/5 + Gi1/0/52 + Gi1/0/54 +2 vlan2 active Gi1/0/6, Gi1/0/7 +1002 fddi-default act/unsup +1003 fddo-default act/unsup \ No newline at end of file diff -Nru ansible-2.5.3/test/units/modules/network/ios/test_ios_vlan.py ansible-2.5.4/test/units/modules/network/ios/test_ios_vlan.py --- ansible-2.5.3/test/units/modules/network/ios/test_ios_vlan.py 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/units/modules/network/ios/test_ios_vlan.py 2018-05-31 22:01:22.000000000 +0000 @@ -0,0 +1,137 @@ +# (c) 2018 Red Hat Inc. +# +# 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 . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible.compat.tests.mock import patch +from ansible.modules.network.ios import ios_vlan +from ansible.modules.network.ios.ios_vlan import parse_vlan_brief +from units.modules.utils import set_module_args +from .ios_module import TestIosModule, load_fixture + + +class TestIosVlanModule(TestIosModule): + + module = ios_vlan + + def setUp(self): + super(TestIosVlanModule, self).setUp() + + self.mock_run_commands = patch('ansible.modules.network.ios.ios_vlan.run_commands') + self.run_commands = self.mock_run_commands.start() + + self.mock_load_config = patch('ansible.modules.network.ios.ios_vlan.load_config') + self.load_config = self.mock_load_config.start() + + def tearDown(self): + super(TestIosVlanModule, self).tearDown() + self.mock_run_commands.stop() + self.mock_load_config.stop() + + def load_fixtures(self, commands=None, transport='cli'): + self.run_commands.return_value = [load_fixture('ios_vlan_config.cfg')] + self.load_config.return_value = {'diff': None, 'session': 'session'} + + def test_ios_vlan_create(self): + set_module_args({'vlan_id': '3', 'name': 'test', 'state': 'present'}) + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'name test', + ] + self.assertEqual(result['commands'], expected_commands) + + def test_ios_vlan_rename(self): + set_module_args({'vlan_id': '2', 'name': 'test', 'state': 'present'}) + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 2', + 'name test', + ] + self.assertEqual(result['commands'], expected_commands) + + def test_ios_vlan_with_interfaces(self): + set_module_args({'vlan_id': '2', 'name': 'vlan2', 'state': 'present', 'interfaces': ['GigabitEthernet1/0/8', 'GigabitEthernet1/0/7']}) + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 2', + 'interface GigabitEthernet1/0/8', + 'switchport mode access', + 'switchport access vlan 2', + 'vlan 2', + 'interface GigabitEthernet1/0/6', + 'switchport mode access', + 'no switchport access vlan 2', + ] + self.assertEqual(result['commands'], expected_commands) + + def test_ios_vlan_with_interfaces_and_newvlan(self): + set_module_args({'vlan_id': '3', 'name': 'vlan3', 'state': 'present', 'interfaces': ['GigabitEthernet1/0/8', 'GigabitEthernet1/0/7']}) + result = self.execute_module(changed=True) + expected_commands = [ + 'vlan 3', + 'name vlan3', + 'interface GigabitEthernet1/0/8', + 'switchport mode access', + 'switchport access vlan 3', + 'interface GigabitEthernet1/0/7', + 'switchport mode access', + 'switchport access vlan 3', + ] + self.assertEqual(result['commands'], expected_commands) + + def test_parse_vlan_brief(self): + result = parse_vlan_brief(load_fixture('ios_vlan_config.cfg')) + obj = [ + { + 'name': 'default', + 'interfaces': [ + 'GigabitEthernet1/0/4', + 'GigabitEthernet1/0/5', + 'GigabitEthernet1/0/52', + 'GigabitEthernet1/0/54', + ], + 'state': 'active', + 'vlan_id': '1', + }, + { + 'name': 'vlan2', + 'interfaces': [ + 'GigabitEthernet1/0/6', + 'GigabitEthernet1/0/7', + ], + 'state': 'active', + 'vlan_id': '2', + }, + { + 'name': 'fddi-default', + 'interfaces': [], + 'state': 'act/unsup', + 'vlan_id': '1002', + }, + { + 'name': 'fddo-default', + 'interfaces': [], + 'state': 'act/unsup', + 'vlan_id': '1003', + }, + ] + self.assertEqual(result, obj) diff -Nru ansible-2.5.3/test/units/modules/system/test_iptables.py ansible-2.5.4/test/units/modules/system/test_iptables.py --- ansible-2.5.3/test/units/modules/system/test_iptables.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/units/modules/system/test_iptables.py 2018-05-31 22:01:22.000000000 +0000 @@ -1,4 +1,3 @@ -from ansible.compat.tests import unittest from ansible.compat.tests.mock import patch from ansible.module_utils import basic from ansible.modules.system import iptables @@ -578,3 +577,62 @@ '--reject-with', 'tcp-reset', ]) + + def test_tcp_flags(self): + """ Test various ways of inputting tcp_flags """ + args = [ + { + 'chain': 'OUTPUT', + 'protocol': 'tcp', + 'jump': 'DROP', + 'tcp_flags': 'flags=ALL flags_set="ACK,RST,SYN,FIN"' + }, + { + 'chain': 'OUTPUT', + 'protocol': 'tcp', + 'jump': 'DROP', + 'tcp_flags': { + 'flags': 'ALL', + 'flags_set': 'ACK,RST,SYN,FIN' + } + }, + { + 'chain': 'OUTPUT', + 'protocol': 'tcp', + 'jump': 'DROP', + 'tcp_flags': { + 'flags': ['ALL'], + 'flags_set': ['ACK', 'RST', 'SYN', 'FIN'] + } + }, + + ] + + for item in args: + set_module_args(item) + + commands_results = [ + (0, '', ''), + ] + + with patch.object(basic.AnsibleModule, 'run_command') as run_command: + run_command.side_effect = commands_results + with self.assertRaises(AnsibleExitJson) as result: + iptables.main() + self.assertTrue(result.exception.args[0]['changed']) + + self.assertEqual(run_command.call_count, 1) + self.assertEqual(run_command.call_args_list[0][0][0], [ + '/sbin/iptables', + '-t', + 'filter', + '-C', + 'OUTPUT', + '-p', + 'tcp', + '--tcp-flags', + 'ALL', + 'ACK,RST,SYN,FIN', + '-j', + 'DROP' + ]) diff -Nru ansible-2.5.3/test/units/playbook/test_play_context.py ansible-2.5.4/test/units/playbook/test_play_context.py --- ansible-2.5.3/test/units/playbook/test_play_context.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/units/playbook/test_play_context.py 2018-05-31 22:01:22.000000000 +0000 @@ -153,8 +153,7 @@ play_context.become_method = 'doas' cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash") - assert (cmd == """%s %s echo %s && %s %s env ANSIBLE=true %s""" % (doas_exe, doas_flags, play_context. - success_key, doas_exe, doas_flags, default_cmd)) + assert (cmd == """%s %s %s -c 'echo %s; %s'""" % (doas_exe, doas_flags, default_exe, play_context.success_key, default_cmd)) play_context.become_method = 'ksu' cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash") diff -Nru ansible-2.5.3/test/units/plugins/action/test_synchronize.py ansible-2.5.4/test/units/plugins/action/test_synchronize.py --- ansible-2.5.3/test/units/plugins/action/test_synchronize.py 2018-05-17 23:51:16.000000000 +0000 +++ ansible-2.5.4/test/units/plugins/action/test_synchronize.py 2018-05-31 22:01:22.000000000 +0000 @@ -42,6 +42,10 @@ ''' +class BreakPoint(Exception): + pass + + class TaskMock(object): args = {'src': u'/tmp/deleteme', 'dest': '/tmp/deleteme', @@ -246,3 +250,15 @@ # delegate to other remote host with su enabled x = SynchronizeTester() x.runtest(fixturepath=os.path.join(self.fixturedir, 'delegate_remote_su')) + + @patch.object(ActionModule, '_low_level_execute_command', side_effect=BreakPoint) + @patch.object(ActionModule, '_remote_expand_user', side_effect=ActionModule._remote_expand_user, autospec=True) + def test_remote_user_not_in_local_tmpdir(self, spy_remote_expand_user, ll_ec): + x = SynchronizeTester() + SAM = ActionModule(x.task, x.connection, x._play_context, + x.loader, x.templar, x.shared_loader_obj) + try: + SAM.run(task_vars={'hostvars': {'foo': {}, 'localhost': {}}, 'inventory_hostname': 'foo'}) + except BreakPoint: + pass + self.assertEqual(spy_remote_expand_user.call_count, 0) diff -Nru ansible-2.5.3/test/units/plugins/connection/test_winrm.py ansible-2.5.4/test/units/plugins/connection/test_winrm.py --- ansible-2.5.3/test/units/plugins/connection/test_winrm.py 1970-01-01 00:00:00.000000000 +0000 +++ ansible-2.5.4/test/units/plugins/connection/test_winrm.py 2018-05-31 22:01:22.000000000 +0000 @@ -0,0 +1,368 @@ +# -*- coding: utf-8 -*- +# (c) 2018, Jordan Borean +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys + +import pytest + +from io import StringIO + +from ansible.compat.tests.mock import patch, MagicMock +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_bytes +from ansible.playbook.play_context import PlayContext +from ansible.plugins.loader import connection_loader + + +class TestConnectionWinRM(object): + + OPTIONS_DATA = ( + # default options + ( + {}, + {'_extras': {}}, + {}, + { + '_kerb_managed': False, + '_kinit_cmd': 'kinit', + '_winrm_connection_timeout': None, + '_winrm_host': 'inventory_hostname', + '_winrm_kwargs': {'username': None, 'password': ''}, + '_winrm_pass': '', + '_winrm_path': '/wsman', + '_winrm_port': 5986, + '_winrm_scheme': 'https', + '_winrm_transport': ['ssl'], + '_winrm_user': None + }, + False + ), + # http through port + ( + {}, + {'_extras': {}, 'ansible_port': 5985}, + {}, + { + '_winrm_kwargs': {'username': None, 'password': ''}, + '_winrm_port': 5985, + '_winrm_scheme': 'http', + '_winrm_transport': ['plaintext'], + }, + False + ), + # kerberos user with kerb present + ( + {}, + {'_extras': {}, 'ansible_user': 'user@domain.com'}, + {}, + { + '_kerb_managed': False, + '_kinit_cmd': 'kinit', + '_winrm_kwargs': {'username': 'user@domain.com', + 'password': ''}, + '_winrm_pass': '', + '_winrm_transport': ['kerberos', 'ssl'], + '_winrm_user': 'user@domain.com' + }, + True + ), + # kerberos user without kerb present + ( + {}, + {'_extras': {}, 'ansible_user': 'user@domain.com'}, + {}, + { + '_kerb_managed': False, + '_kinit_cmd': 'kinit', + '_winrm_kwargs': {'username': 'user@domain.com', + 'password': ''}, + '_winrm_pass': '', + '_winrm_transport': ['ssl'], + '_winrm_user': 'user@domain.com' + }, + False + ), + # kerberos user with managed ticket (implicit) + ( + {'password': 'pass'}, + {'_extras': {}, 'ansible_user': 'user@domain.com'}, + {}, + { + '_kerb_managed': True, + '_kinit_cmd': 'kinit', + '_winrm_kwargs': {'username': 'user@domain.com', + 'password': 'pass'}, + '_winrm_pass': 'pass', + '_winrm_transport': ['kerberos', 'ssl'], + '_winrm_user': 'user@domain.com' + }, + True + ), + # kerb with managed ticket (explicit) + ( + {'password': 'pass'}, + {'_extras': {}, 'ansible_user': 'user@domain.com', + 'ansible_winrm_kinit_mode': 'managed'}, + {}, + { + '_kerb_managed': True, + }, + True + ), + # kerb with unmanaged ticket (explicit)) + ( + {'password': 'pass'}, + {'_extras': {}, 'ansible_user': 'user@domain.com', + 'ansible_winrm_kinit_mode': 'manual'}, + {}, + { + '_kerb_managed': False, + }, + True + ), + # transport override (single) + ( + {}, + {'_extras': {}, 'ansible_user': 'user@domain.com', + 'ansible_winrm_transport': 'ntlm'}, + {}, + { + '_winrm_kwargs': {'username': 'user@domain.com', + 'password': ''}, + '_winrm_pass': '', + '_winrm_transport': ['ntlm'], + }, + False + ), + # transport override (list) + ( + {}, + {'_extras': {}, 'ansible_user': 'user@domain.com', + 'ansible_winrm_transport': ['ntlm', 'certificate']}, + {}, + { + '_winrm_kwargs': {'username': 'user@domain.com', + 'password': ''}, + '_winrm_pass': '', + '_winrm_transport': ['ntlm', 'certificate'], + }, + False + ), + # winrm extras + ( + {}, + {'_extras': {'ansible_winrm_server_cert_validation': 'ignore', + 'ansible_winrm_service': 'WSMAN'}}, + {}, + { + '_winrm_kwargs': {'username': None, 'password': '', + 'server_cert_validation': 'ignore', + 'service': 'WSMAN'}, + }, + False + ), + # direct override + ( + {}, + {'_extras': {}, 'ansible_winrm_connection_timeout': 5}, + {'connection_timeout': 10}, + { + '_winrm_connection_timeout': 10, + }, + False + ), + # user comes from option not play context + ( + {'username': 'user1'}, + {'_extras': {}, 'ansible_user': 'user2'}, + {}, + { + '_winrm_user': 'user2', + '_winrm_kwargs': {'username': 'user2', 'password': ''} + }, + False + ) + ) + + # pylint bug: https://github.com/PyCQA/pylint/issues/511 + # pylint: disable=undefined-variable + @pytest.mark.parametrize('play, options, direct, expected, kerb', + ((p, o, d, e, k) for p, o, d, e, k in OPTIONS_DATA)) + def test_set_options(self, play, options, direct, expected, kerb): + if kerb: + kerberos_mock = MagicMock() + modules = {'kerberos': kerberos_mock} + else: + modules = {'kerberos': None} + + module_patcher = patch.dict(sys.modules, modules) + module_patcher.start() + + pc = PlayContext() + for attr, value in play.items(): + setattr(pc, attr, value) + + new_stdin = StringIO() + + # ensure we get a fresh connection plugin by clearing the cache + connection_loader._module_cache = {} + conn = connection_loader.get('winrm', pc, new_stdin) + conn.set_options(var_options=options, direct=direct) + + for attr, expected in expected.items(): + actual = getattr(conn, attr) + assert actual == expected, \ + "winrm attr '%s', actual '%s' != expected '%s'"\ + % (attr, actual, expected) + + module_patcher.stop() + + +class TestWinRMKerbAuth(object): + + DATA = ( + # default + ({"_extras": {}}, (["kinit", "user@domain"],), False), + ({"_extras": {}}, ("kinit", ["user@domain"],), True), + + # override kinit path from options + ({"_extras": {}, 'ansible_winrm_kinit_cmd': 'kinit2'}, + (["kinit2", "user@domain"],), False), + ({"_extras": {}, 'ansible_winrm_kinit_cmd': 'kinit2'}, + ("kinit2", ["user@domain"],), True), + + # we expect the -f flag when delegation is set + ({"_extras": {'ansible_winrm_kerberos_delegation': True}}, + (["kinit", "-f", "user@domain"],), False), + ({"_extras": {'ansible_winrm_kerberos_delegation': True}}, + ("kinit", ["-f", "user@domain"],), True), + ) + + # pylint bug: https://github.com/PyCQA/pylint/issues/511 + # pylint: disable=undefined-variable + @pytest.mark.parametrize('options, expected, pexpect', + ((o, e, p) for o, e, p in DATA)) + def test_kinit_success(self, options, expected, pexpect): + def mock_popen_communicate(input=None, timeout=None): + return b"", b"" + + mock_pexpect = None + if pexpect: + mock_pexpect = MagicMock() + mock_pexpect.spawn.return_value.exitstatus = 0 + + mock_subprocess = MagicMock() + mock_subprocess.Popen.return_value.communicate = mock_popen_communicate + mock_subprocess.Popen.return_value.returncode = 0 + + modules = { + 'pexpect': mock_pexpect, + 'subprocess': mock_subprocess, + } + + with patch.dict(sys.modules, modules): + pc = PlayContext() + new_stdin = StringIO() + + connection_loader._module_cache = {} + conn = connection_loader.get('winrm', pc, new_stdin) + conn.set_options(var_options=options) + + conn._kerb_auth("user@domain", "pass") + if pexpect: + assert len(mock_pexpect.method_calls) == 1 + assert mock_pexpect.method_calls[0][1] == expected + actual_env = mock_pexpect.method_calls[0][2]['env'] + else: + assert len(mock_subprocess.method_calls) == 1 + assert mock_subprocess.method_calls[0][1] == expected + actual_env = mock_subprocess.method_calls[0][2]['env'] + + assert list(actual_env.keys()) == ['KRB5CCNAME'] + assert actual_env['KRB5CCNAME'].startswith("FILE:/") + + # pylint bug: https://github.com/PyCQA/pylint/issues/511 + # pylint: disable=undefined-variable + @pytest.mark.parametrize('use_pexpect', (False, True),) + def test_kinit_with_missing_executable(self, use_pexpect): + expected_err = "[Errno 2] No such file or directory: " \ + "'/fake/kinit': '/fake/kinit'" + mock_subprocess = MagicMock() + mock_subprocess.Popen = MagicMock(side_effect=OSError(expected_err)) + + mock_pexpect = None + if use_pexpect: + expected_err = "The command was not found or was not " \ + "executable: /fake/kinit" + + mock_pexpect = MagicMock() + mock_pexpect.ExceptionPexpect = Exception + mock_pexpect.spawn = MagicMock(side_effect=Exception(expected_err)) + + modules = { + 'pexpect': mock_pexpect, + 'subprocess': mock_subprocess, + } + + with patch.dict(sys.modules, modules): + pc = PlayContext() + new_stdin = StringIO() + + connection_loader._module_cache = {} + conn = connection_loader.get('winrm', pc, new_stdin) + options = {"_extras": {}, "ansible_winrm_kinit_cmd": "/fake/kinit"} + conn.set_options(var_options=options) + + with pytest.raises(AnsibleConnectionFailure) as err: + conn._kerb_auth("user@domain", "pass") + assert str(err.value) == "Kerberos auth failure when calling " \ + "kinit cmd '/fake/kinit': %s" % expected_err + + # pylint bug: https://github.com/PyCQA/pylint/issues/511 + # pylint: disable=undefined-variable + @pytest.mark.parametrize('use_pexpect', (False, True),) + def test_kinit_error(self, use_pexpect): + mechanism = "subprocess" + expected_err = "kinit: krb5_parse_name: " \ + "Configuration file does not specify default realm" + + def mock_popen_communicate(input=None, timeout=None): + return b"", to_bytes(expected_err) + + mock_subprocess = MagicMock() + mock_subprocess.Popen.return_value.communicate = mock_popen_communicate + mock_subprocess.Popen.return_value.returncode = 1 + + mock_pexpect = None + if use_pexpect: + mechanism = "pexpect" + expected_err = "Configuration file does not specify default realm" + + mock_pexpect = MagicMock() + mock_pexpect.spawn.return_value.expect = MagicMock(side_effect=OSError) + mock_pexpect.spawn.return_value.read.return_value = to_bytes(expected_err) + mock_pexpect.spawn.return_value.exitstatus = 1 + + modules = { + 'pexpect': mock_pexpect, + 'subprocess': mock_subprocess, + } + + with patch.dict(sys.modules, modules): + pc = PlayContext() + new_stdin = StringIO() + + connection_loader._module_cache = {} + conn = connection_loader.get('winrm', pc, new_stdin) + conn.set_options(var_options={"_extras": {}}) + + with pytest.raises(AnsibleConnectionFailure) as err: + conn._kerb_auth("invaliduser", "pass") + + assert str(err.value) == "Kerberos auth failure for principal " \ + "invaliduser with %s: %s" % (mechanism, expected_err)