summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2018-04-20 04:29:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2018-04-20 04:29:30 +0000
commitafaa98221c03d8e24cef06d00b205a9e3fef9eab (patch)
tree2e79a5ba3480e6255a030a297bfeeece8d0fc136
parentReleasing progress-linux version 2.5.0+dfsg-1~dschinn1. (diff)
downloadansible-afaa98221c03d8e24cef06d00b205a9e3fef9eab.zip
ansible-afaa98221c03d8e24cef06d00b205a9e3fef9eab.tar.xz
Merging upstream version 2.5.1+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--MANIFEST.in2
-rw-r--r--Makefile13
-rw-r--r--PKG-INFO112
-rw-r--r--README.md56
-rw-r--r--README.rst100
-rw-r--r--changelogs/CHANGELOG-v2.5.rst203
-rwxr-xr-xcontrib/inventory/cloudforms.py2
-rwxr-xr-xcontrib/inventory/foreman.py2
-rwxr-xr-xcontrib/inventory/ovirt4.py2
-rwxr-xr-xcontrib/inventory/vagrant.py13
-rwxr-xr-xcontrib/inventory/vmware_inventory.py2
-rwxr-xr-xdocs/bin/plugin_formatter.py1
-rwxr-xr-xdocs/bin/testing_formatter.sh10
-rw-r--r--docs/man/man1/ansible-config.16
-rw-r--r--docs/man/man1/ansible-console.16
-rw-r--r--docs/man/man1/ansible-doc.16
-rw-r--r--docs/man/man1/ansible-galaxy.16
-rw-r--r--docs/man/man1/ansible-inventory.116
-rw-r--r--docs/man/man1/ansible-inventory.1.asciidoc.in8
-rw-r--r--docs/man/man1/ansible-playbook.16
-rw-r--r--docs/man/man1/ansible-pull.16
-rw-r--r--docs/man/man1/ansible-vault.111
-rw-r--r--docs/man/man1/ansible-vault.1.asciidoc.in6
-rw-r--r--docs/man/man1/ansible.16
-rw-r--r--docs/templates/config.rst.j22
-rw-r--r--docs/templates/list_of_CATEGORY_modules.rst.j29
-rw-r--r--docs/templates/list_of_CATEGORY_plugins.rst.j22
-rw-r--r--docs/templates/modules_by_support.rst.j24
-rw-r--r--docs/templates/plugin.rst.j238
-rw-r--r--lib/ansible/cli/doc.py3
-rw-r--r--lib/ansible/cli/inventory.py11
-rw-r--r--lib/ansible/cli/pull.py10
-rw-r--r--lib/ansible/cli/vault.py4
-rw-r--r--lib/ansible/constants.py4
-rw-r--r--lib/ansible/executor/module_common.py4
-rw-r--r--lib/ansible/executor/task_executor.py31
-rw-r--r--lib/ansible/inventory/data.py2
-rw-r--r--lib/ansible/inventory/group.py89
-rw-r--r--lib/ansible/inventory/host.py13
-rw-r--r--lib/ansible/inventory/manager.py2
-rw-r--r--lib/ansible/module_utils/connection.py7
-rw-r--r--lib/ansible/module_utils/facts/hardware/freebsd.py63
-rw-r--r--lib/ansible/module_utils/facts/system/chroot.py2
-rw-r--r--lib/ansible/module_utils/facts/utils.py2
-rw-r--r--lib/ansible/module_utils/facts/virtual/linux.py16
-rw-r--r--lib/ansible/module_utils/k8s/lookup.py6
-rw-r--r--lib/ansible/module_utils/network/eos/eos.py45
-rw-r--r--lib/ansible/module_utils/network/junos/junos.py4
-rw-r--r--lib/ansible/module_utils/network/nso/nso.py51
-rw-r--r--lib/ansible/module_utils/network/onyx/onyx.py11
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm183
-rw-r--r--lib/ansible/module_utils/powershell/Ansible.ModuleUtils.FileUtil.psm12
-rw-r--r--lib/ansible/module_utils/vmware.py55
-rw-r--r--lib/ansible/modules/cloud/amazon/aws_s3.py2
-rw-r--r--lib/ansible/modules/cloud/amazon/ec2.py12
-rw-r--r--lib/ansible/modules/cloud/amazon/ec2_asg.py18
-rw-r--r--lib/ansible/modules/cloud/amazon/ec2_group.py2
-rw-r--r--lib/ansible/modules/cloud/azure/azure_rm_image.py11
-rw-r--r--lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py98
-rw-r--r--lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py5
-rw-r--r--lib/ansible/modules/cloud/cloudstack/cs_configuration.py4
-rw-r--r--lib/ansible/modules/cloud/cloudstack/cs_vpc_offering.py4
-rw-r--r--lib/ansible/modules/cloud/digital_ocean/digital_ocean_domain.py3
-rw-r--r--lib/ansible/modules/cloud/ovirt/ovirt_cluster.py4
-rw-r--r--lib/ansible/modules/cloud/ovirt/ovirt_host_networks.py102
-rw-r--r--lib/ansible/modules/cloud/ovirt/ovirt_hosts.py4
-rw-r--r--lib/ansible/modules/cloud/vmware/vcenter_license.py2
-rw-r--r--lib/ansible/modules/cloud/vmware/vmware_cfg_backup.py12
-rw-r--r--lib/ansible/modules/cloud/vmware/vmware_guest.py5
-rw-r--r--lib/ansible/modules/cloud/vmware/vmware_guest_find.py34
-rw-r--r--lib/ansible/modules/clustering/consul_kv.py34
-rw-r--r--lib/ansible/modules/crypto/openssl_certificate.py33
-rw-r--r--lib/ansible/modules/monitoring/grafana_dashboard.py81
-rw-r--r--lib/ansible/modules/monitoring/grafana_datasource.py166
-rw-r--r--lib/ansible/modules/monitoring/grafana_plugin.py20
-rw-r--r--lib/ansible/modules/monitoring/pagerduty.py5
-rw-r--r--lib/ansible/modules/net_tools/haproxy.py10
-rw-r--r--lib/ansible/modules/net_tools/nios/nios_host_record.py4
-rw-r--r--lib/ansible/modules/net_tools/nmcli.py4
-rwxr-xr-xlib/ansible/modules/network/aci/aci_domain_to_vlan_pool.py2
-rw-r--r--lib/ansible/modules/network/citrix/_netscaler.py6
-rw-r--r--lib/ansible/modules/network/edgeos/edgeos_config.py2
-rw-r--r--lib/ansible/modules/network/edgeos/edgeos_facts.py2
-rw-r--r--lib/ansible/modules/network/eos/eos_config.py5
-rw-r--r--lib/ansible/modules/network/eos/eos_vlan.py24
-rw-r--r--lib/ansible/modules/network/ios/ios_config.py5
-rw-r--r--lib/ansible/modules/network/ios/ios_interface.py33
-rw-r--r--lib/ansible/modules/network/ios/ios_l2_interface.py29
-rw-r--r--lib/ansible/modules/network/iosxr/iosxr_config.py5
-rw-r--r--lib/ansible/modules/network/junos/junos_banner.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_command.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_config.py7
-rw-r--r--lib/ansible/modules/network/junos/junos_facts.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_interface.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_l2_interface.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_l3_interface.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_linkagg.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_lldp.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_lldp_interface.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_logging.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_netconf.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_package.py1
-rw-r--r--lib/ansible/modules/network/junos/junos_rpc.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_scp.py1
-rw-r--r--lib/ansible/modules/network/junos/junos_static_route.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_system.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_user.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_vlan.py2
-rw-r--r--lib/ansible/modules/network/junos/junos_vrf.py2
-rw-r--r--lib/ansible/modules/network/nso/nso_verify.py18
-rw-r--r--lib/ansible/modules/network/nxos/_nxos_switchport.py27
-rw-r--r--lib/ansible/modules/network/nxos/nxos_aaa_server.py55
-rw-r--r--lib/ansible/modules/network/nxos/nxos_aaa_server_host.py163
-rw-r--r--lib/ansible/modules/network/nxos/nxos_acl.py33
-rw-r--r--lib/ansible/modules/network/nxos/nxos_bgp_af.py12
-rw-r--r--lib/ansible/modules/network/nxos/nxos_config.py5
-rw-r--r--lib/ansible/modules/network/nxos/nxos_hsrp.py183
-rw-r--r--lib/ansible/modules/network/nxos/nxos_igmp.py13
-rw-r--r--lib/ansible/modules/network/nxos/nxos_igmp_snooping.py32
-rw-r--r--lib/ansible/modules/network/nxos/nxos_l2_interface.py21
-rw-r--r--lib/ansible/modules/network/nxos/nxos_ntp_auth.py71
-rw-r--r--lib/ansible/modules/network/nxos/nxos_ntp_options.py65
-rw-r--r--lib/ansible/modules/network/nxos/nxos_snapshot.py31
-rw-r--r--lib/ansible/modules/network/nxos/nxos_static_route.py87
-rw-r--r--lib/ansible/modules/network/nxos/nxos_udld.py81
-rw-r--r--lib/ansible/modules/network/nxos/nxos_vlan.py107
-rw-r--r--lib/ansible/modules/network/nxos/nxos_vrf.py24
-rw-r--r--lib/ansible/modules/network/nxos/nxos_vrf_af.py16
-rw-r--r--lib/ansible/modules/network/onyx/onyx_linkagg.py18
-rw-r--r--lib/ansible/modules/network/onyx/onyx_pfc_interface.py12
-rw-r--r--lib/ansible/modules/network/onyx/onyx_vlan.py3
-rw-r--r--lib/ansible/modules/network/vyos/vyos_config.py4
-rw-r--r--lib/ansible/modules/packaging/os/zypper.py3
-rw-r--r--lib/ansible/modules/source_control/github_hooks.py5
-rwxr-xr-xlib/ansible/modules/system/interfaces_file.py7
-rw-r--r--lib/ansible/modules/system/puppet.py20
-rw-r--r--lib/ansible/modules/system/user.py11
-rw-r--r--lib/ansible/modules/web_infrastructure/jenkins_script.py15
-rw-r--r--lib/ansible/modules/web_infrastructure/jira.py4
-rw-r--r--lib/ansible/modules/web_infrastructure/letsencrypt.py40
-rw-r--r--lib/ansible/modules/windows/setup.ps139
-rw-r--r--lib/ansible/modules/windows/win_certificate_store.ps12
-rw-r--r--lib/ansible/modules/windows/win_certificate_store.py8
-rw-r--r--lib/ansible/modules/windows/win_regedit.ps11
-rw-r--r--lib/ansible/modules/windows/win_service.ps1130
-rw-r--r--lib/ansible/modules/windows/win_service.py4
-rw-r--r--lib/ansible/modules/windows/win_uri.ps115
-rw-r--r--lib/ansible/parsing/vault/__init__.py32
-rw-r--r--lib/ansible/playbook/conditional.py2
-rw-r--r--lib/ansible/playbook/included_file.py9
-rw-r--r--lib/ansible/playbook/role_include.py4
-rw-r--r--lib/ansible/playbook/task.py5
-rw-r--r--lib/ansible/plugins/action/__init__.py4
-rw-r--r--lib/ansible/plugins/action/edgeos_config.py113
-rw-r--r--lib/ansible/plugins/action/include_vars.py2
-rw-r--r--lib/ansible/plugins/action/junos.py16
-rw-r--r--lib/ansible/plugins/action/onyx_config.py2
-rw-r--r--lib/ansible/plugins/action/vyos_config.py4
-rw-r--r--lib/ansible/plugins/action/wait_for_connection.py3
-rw-r--r--lib/ansible/plugins/action/win_copy.py5
-rw-r--r--lib/ansible/plugins/action/win_reboot.py2
-rw-r--r--lib/ansible/plugins/action/win_updates.py12
-rw-r--r--lib/ansible/plugins/callback/foreman.py4
-rw-r--r--lib/ansible/plugins/callback/oneline.py14
-rw-r--r--lib/ansible/plugins/callback/slack.py2
-rw-r--r--lib/ansible/plugins/callback/yaml.py2
-rw-r--r--lib/ansible/plugins/cliconf/eos.py11
-rw-r--r--lib/ansible/plugins/cliconf/junos.py6
-rw-r--r--lib/ansible/plugins/connection/netconf.py10
-rw-r--r--lib/ansible/plugins/connection/network_cli.py8
-rw-r--r--lib/ansible/plugins/connection/persistent.py21
-rw-r--r--lib/ansible/plugins/connection/winrm.py18
-rw-r--r--lib/ansible/plugins/inventory/__init__.py70
-rw-r--r--lib/ansible/plugins/inventory/auto.py2
-rw-r--r--lib/ansible/plugins/inventory/constructed.py3
-rw-r--r--lib/ansible/plugins/inventory/ini.py3
-rw-r--r--lib/ansible/plugins/inventory/script.py19
-rw-r--r--lib/ansible/plugins/inventory/yaml.py2
-rw-r--r--lib/ansible/plugins/loader.py14
-rw-r--r--lib/ansible/plugins/lookup/csvfile.py23
-rw-r--r--lib/ansible/plugins/lookup/first_found.py70
-rw-r--r--lib/ansible/plugins/shell/powershell.py28
-rw-r--r--lib/ansible/release.py2
-rw-r--r--lib/ansible/template/__init__.py7
-rw-r--r--lib/ansible/utils/module_docs_fragments/vmware.py70
-rw-r--r--packaging/arch/PKGBUILD2
-rw-r--r--packaging/debian/docs2
-rw-r--r--packaging/macports/sysutils/ansible/Portfile2
-rw-r--r--packaging/release/vars/versions.yml1
-rw-r--r--packaging/rpm/ansible.spec33
-rw-r--r--setup.py144
-rw-r--r--test/integration/targets/azure_rm_networkinterface/tasks/main.yml16
-rw-r--r--test/integration/targets/docker_secret/tasks/test_secrets.yml2
-rw-r--r--test/integration/targets/ec2_asg/tasks/main.yml116
-rw-r--r--test/integration/targets/ec2_group/tasks/main.yml6
-rw-r--r--test/integration/targets/eos_config/templates/basic/cmds.j24
-rw-r--r--test/integration/targets/eos_config/tests/cli/check_mode.yaml69
-rw-r--r--test/integration/targets/eos_facts/tests/eapi/default_facts.yaml11
-rw-r--r--test/integration/targets/eos_facts/tests/eapi/not_hardware.yaml11
-rw-r--r--test/integration/targets/eos_smoke/defaults/main.yaml2
-rw-r--r--test/integration/targets/eos_smoke/meta/main.yml2
-rw-r--r--test/integration/targets/eos_smoke/tasks/cli.yaml22
-rw-r--r--test/integration/targets/eos_smoke/tasks/eapi.yaml16
-rw-r--r--test/integration/targets/eos_smoke/tasks/main.yaml3
-rw-r--r--test/integration/targets/eos_smoke/tests/cli/common_config.yaml108
-rw-r--r--test/integration/targets/eos_smoke/tests/cli/common_utils.yaml72
-rw-r--r--test/integration/targets/eos_smoke/tests/cli/misc_tests.yaml26
-rw-r--r--test/integration/targets/eos_smoke/tests/eapi/common_config.yaml99
-rw-r--r--test/integration/targets/eos_smoke/tests/eapi/common_utils.yaml66
-rw-r--r--test/integration/targets/eos_smoke/tests/eapi/misc_tests.yaml26
-rw-r--r--test/integration/targets/fortios_address/aliases1
-rw-r--r--test/integration/targets/fortios_ipv4_policy/aliases1
-rw-r--r--test/integration/targets/include_import/role/test_include_role.yml8
-rw-r--r--test/integration/targets/include_import/roles/role2/tasks/main.yml1
-rw-r--r--test/integration/targets/include_import/roles/role3/tasks/main.yml1
-rw-r--r--test/integration/targets/ios_l2_interface/tests/cli/sanity.yaml35
-rw-r--r--test/integration/targets/iosxr_smoke/defaults/main.yaml3
-rw-r--r--test/integration/targets/iosxr_smoke/meta/main.yaml2
-rw-r--r--test/integration/targets/iosxr_smoke/tasks/cli.yaml24
-rw-r--r--test/integration/targets/iosxr_smoke/tasks/main.yaml3
-rw-r--r--test/integration/targets/iosxr_smoke/tasks/netconf.yaml24
-rw-r--r--test/integration/targets/iosxr_smoke/tests/cli/common_config.yaml100
-rw-r--r--test/integration/targets/iosxr_smoke/tests/cli/common_utils.yaml37
-rw-r--r--test/integration/targets/iosxr_smoke/tests/netconf/common_netconf.yaml53
-rw-r--r--test/integration/targets/iosxr_smoke/tests/netconf/misc_tests.yaml39
-rw-r--r--test/integration/targets/junos_banner/tests/netconf/basic.yaml14
-rw-r--r--test/integration/targets/junos_smoke/defaults/main.yaml3
-rw-r--r--test/integration/targets/junos_smoke/meta/main.yml2
-rw-r--r--test/integration/targets/junos_smoke/tasks/main.yaml2
-rw-r--r--test/integration/targets/junos_smoke/tasks/netconf.yaml21
-rw-r--r--test/integration/targets/junos_smoke/tests/netconf/common_utils.yaml56
-rw-r--r--test/integration/targets/junos_smoke/tests/netconf/module_utils_junos.yaml99
-rw-r--r--test/integration/targets/nxos_aaa_server/tests/common/radius.yaml10
-rw-r--r--test/integration/targets/nxos_aaa_server/tests/common/tacacs.yaml17
-rw-r--r--test/integration/targets/nxos_aaa_server_host/tests/common/radius.yaml68
-rw-r--r--test/integration/targets/nxos_aaa_server_host/tests/common/tacacs.yaml61
-rw-r--r--test/integration/targets/nxos_acl/tests/common/sanity.yaml184
-rw-r--r--test/integration/targets/nxos_acl_interface/tests/common/sanity.yaml14
-rw-r--r--test/integration/targets/nxos_hsrp/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_hsrp/tests/common/sanity.yaml127
-rw-r--r--test/integration/targets/nxos_hsrp/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_igmp/tests/common/sanity.yaml33
-rw-r--r--test/integration/targets/nxos_igmp_snooping/tests/common/sanity.yaml57
-rw-r--r--test/integration/targets/nxos_l2_interface/tests/common/sanity.yaml34
-rw-r--r--test/integration/targets/nxos_ntp/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_ntp/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_ntp_auth/tests/common/sanity.yaml89
-rw-r--r--test/integration/targets/nxos_ntp_options/tests/common/sanity.yaml58
-rw-r--r--test/integration/targets/nxos_ospf_vrf/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_ospf_vrf/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_overlay_global/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_overlay_global/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_pim_interface/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_pim_interface/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_smoke/defaults/main.yaml3
-rw-r--r--test/integration/targets/nxos_smoke/meta/main.yml2
-rw-r--r--test/integration/targets/nxos_smoke/tasks/cli.yaml33
-rw-r--r--test/integration/targets/nxos_smoke/tasks/main.yaml3
-rw-r--r--test/integration/targets/nxos_smoke/tasks/nxapi.yaml27
-rw-r--r--test/integration/targets/nxos_smoke/tests/common/common_config.yaml160
-rw-r--r--test/integration/targets/nxos_smoke/tests/common/common_utils.yaml101
-rw-r--r--test/integration/targets/nxos_smoke/tests/common/misc_tests.yaml24
-rw-r--r--test/integration/targets/nxos_snapshot/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_snapshot/tests/common/sanity.yaml16
-rw-r--r--test/integration/targets/nxos_snapshot/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_snmp_community/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_snmp_community/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_snmp_location/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_snmp_location/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_snmp_user/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_snmp_user/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_static_route/defaults/main.yaml3
-rw-r--r--test/integration/targets/nxos_static_route/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_static_route/tests/common/sanity.yaml33
-rw-r--r--test/integration/targets/nxos_static_route/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_switchport/tests/common/sanity.yaml33
-rw-r--r--test/integration/targets/nxos_udld/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_udld/tests/common/sanity.yaml58
-rw-r--r--test/integration/targets/nxos_udld/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_udld_interface/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_udld_interface/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_user/tests/common/sanity.yaml117
-rw-r--r--test/integration/targets/nxos_vlan/tests/common/agg.yaml21
-rw-r--r--test/integration/targets/nxos_vlan/tests/common/interface.yaml1
-rw-r--r--test/integration/targets/nxos_vlan/tests/common/sanity.yaml130
-rw-r--r--test/integration/targets/nxos_vpc/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_vpc/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_vpc_interface/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_vpc_interface/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_vrf/tests/common/sanity.yaml72
-rw-r--r--test/integration/targets/nxos_vrf_af/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_vrf_af/tests/common/sanity.yaml108
-rw-r--r--test/integration/targets/nxos_vrf_af/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_vxlan_vtep_vni/tests/cli/sanity.yaml4
-rw-r--r--test/integration/targets/nxos_vxlan_vtep_vni/tests/nxapi/sanity.yaml4
-rw-r--r--test/integration/targets/openssl_certificate/tasks/main.yml9
-rwxr-xr-xtest/integration/targets/vault/runme.sh27
-rw-r--r--test/integration/targets/vmware_guest/tasks/create_d1_c1_f0_env.yml137
-rw-r--r--test/integration/targets/vmware_guest/tasks/network_negative_test.yml48
-rw-r--r--test/integration/targets/win_async_wrapper/tasks/main.yml14
-rw-r--r--test/integration/targets/win_become/tasks/main.yml14
-rw-r--r--test/integration/targets/win_command/tasks/main.yml13
-rw-r--r--test/integration/targets/win_copy/tasks/main.yml10
-rw-r--r--test/integration/targets/win_exec_wrapper/aliases3
-rw-r--r--test/integration/targets/win_exec_wrapper/tasks/main.yml2
-rw-r--r--test/integration/targets/win_module_utils/library/file_util_test.ps18
-rw-r--r--test/integration/targets/win_regedit/tasks/tests.yml10
-rw-r--r--test/integration/targets/win_service/defaults/main.yml4
-rw-r--r--test/integration/targets/win_service/tasks/tests.yml44
-rw-r--r--test/integration/targets/win_setup/tasks/main.yml2
-rw-r--r--test/integration/targets/win_shell/tasks/main.yml26
-rw-r--r--test/integration/targets/win_uri/tasks/test.yml54
-rw-r--r--test/runner/lib/cloud/vcenter.py2
-rwxr-xr-xtest/runner/test.py2
-rw-r--r--test/sanity/import/lib/ansible/module_utils/__init__.pycbin165 -> 165 bytes
-rw-r--r--test/sanity/import/lib/ansible/module_utils/_text.pycbin8872 -> 8872 bytes
-rw-r--r--test/sanity/import/lib/ansible/module_utils/basic.pycbin86152 -> 86156 bytes
-rw-r--r--test/sanity/import/lib/ansible/module_utils/connection.py7
-rw-r--r--test/sanity/import/lib/ansible/module_utils/facts/hardware/freebsd.py63
-rw-r--r--test/sanity/import/lib/ansible/module_utils/facts/system/chroot.py2
-rw-r--r--test/sanity/import/lib/ansible/module_utils/facts/utils.py2
-rw-r--r--test/sanity/import/lib/ansible/module_utils/facts/virtual/linux.py16
-rw-r--r--test/sanity/import/lib/ansible/module_utils/k8s/lookup.py6
-rw-r--r--test/sanity/import/lib/ansible/module_utils/network/eos/eos.py45
-rw-r--r--test/sanity/import/lib/ansible/module_utils/network/junos/junos.py4
-rw-r--r--test/sanity/import/lib/ansible/module_utils/network/nso/nso.py51
-rw-r--r--test/sanity/import/lib/ansible/module_utils/network/onyx/onyx.py11
-rw-r--r--test/sanity/import/lib/ansible/module_utils/parsing/__init__.pycbin173 -> 173 bytes
-rw-r--r--test/sanity/import/lib/ansible/module_utils/parsing/convert_bool.pycbin1505 -> 1505 bytes
-rw-r--r--test/sanity/import/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm183
-rw-r--r--test/sanity/import/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.FileUtil.psm12
-rw-r--r--test/sanity/import/lib/ansible/module_utils/pycompat24.pycbin2768 -> 2768 bytes
-rw-r--r--test/sanity/import/lib/ansible/module_utils/six/__init__.pycbin34210 -> 34210 bytes
-rw-r--r--test/sanity/import/lib/ansible/module_utils/urls.pycbin33081 -> 33081 bytes
-rw-r--r--test/sanity/import/lib/ansible/module_utils/vmware.py55
-rw-r--r--test/sanity/pep8/current-ignore.txt1
-rw-r--r--test/sanity/pslint/ignore.txt1
-rw-r--r--test/units/executor/module_common/test_modify_module.py6
-rw-r--r--test/units/mock/loader.py2
-rw-r--r--test/units/modules/network/nso/nso_module.py6
-rw-r--r--test/units/modules/network/nso/test_nso_verify.py2
-rw-r--r--test/units/modules/network/nxos/test_nxos_hsrp.py2
-rw-r--r--test/units/modules/network/onyx/test_onyx_linkagg.py5
-rw-r--r--test/units/modules/network/onyx/test_onyx_pfc_interface.py5
-rw-r--r--test/units/modules/network/onyx/test_onyx_vlan.py8
-rw-r--r--test/units/modules/system/interfaces_file/fixtures/golden_output/default_dhcp_revert6
-rw-r--r--test/units/modules/system/interfaces_file/fixtures/golden_output/default_dhcp_revert.exceptions.txt0
-rw-r--r--test/units/modules/system/interfaces_file/fixtures/golden_output/default_dhcp_revert.json18
-rw-r--r--test/units/modules/system/interfaces_file/fixtures/golden_output/servers.com_revert58
-rw-r--r--test/units/modules/system/interfaces_file/fixtures/golden_output/servers.com_revert.exceptions.txt8
-rw-r--r--test/units/modules/system/interfaces_file/fixtures/golden_output/servers.com_revert.json101
-rw-r--r--test/units/modules/system/interfaces_file/test_interfaces_file.py57
-rw-r--r--test/units/parsing/vault/test_vault.py20
-rw-r--r--test/units/playbook/test_task.py29
-rw-r--r--test/units/plugins/connection/test_network_cli.py2
-rw-r--r--test/units/plugins/inventory/test_group.py125
-rw-r--r--test/units/plugins/inventory/test_inventory.py2
-rw-r--r--test/units/template/test_tests_as_filters_warning.py7
358 files changed, 6442 insertions, 2040 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index deee3ff..826e09a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,6 @@
prune ticket_stubs
prune hacking
-include README.md COPYING
+include README.rst COPYING
include SYMLINK_CACHE.json
include requirements.txt
include .coveragerc
diff --git a/Makefile b/Makefile
index c495ca0..f34eeb6 100644
--- a/Makefile
+++ b/Makefile
@@ -111,6 +111,11 @@ RPMNVR = "$(NAME)-$(RPMVERSION)-$(RPMRELEASE)$(RPMDIST)$(REPOTAG)"
MOCK_BIN ?= mock
MOCK_CFG ?=
+# dynamically add repotag define only if specified
+ifneq ($(REPOTAG),)
+ EXTRA_RPM_DEFINES += --define "repotag $(REPOTAG)"
+endif
+
# ansible-test parameters
ANSIBLE_TEST ?= test/runner/ansible-test
TEST_FLAGS ?=
@@ -249,7 +254,7 @@ mock-srpm: /etc/mock/$(MOCK_CFG).cfg rpmcommon
--define "rpmversion $(RPMVERSION)" \
--define "upstream_version $(VERSION)" \
--define "rpmrelease $(RPMRELEASE)" \
- --define "repotag $(REPOTAG)"
+ $(EXTRA_RPM_DEFINES)
@echo "#############################################"
@echo "Ansible SRPM is built:"
@echo rpm-build/*.src.rpm
@@ -261,7 +266,7 @@ mock-rpm: /etc/mock/$(MOCK_CFG).cfg mock-srpm
--define "rpmversion $(RPMVERSION)" \
--define "upstream_version $(VERSION)" \
--define "rpmrelease $(RPMRELEASE)" \
- --define "repotag $(REPOTAG)"
+ $(EXTRA_RPM_DEFINES)
@echo "#############################################"
@echo "Ansible RPM is built:"
@echo rpm-build/*.noarch.rpm
@@ -278,7 +283,7 @@ srpm: rpmcommon
--define "upstream_version $(VERSION)" \
--define "rpmversion $(RPMVERSION)" \
--define "rpmrelease $(RPMRELEASE)" \
- --define "repotag $(REPOTAG)" \
+ $(EXTRA_RPM_DEFINES) \
-bs rpm-build/$(NAME).spec
@rm -f rpm-build/$(NAME).spec
@echo "#############################################"
@@ -299,7 +304,7 @@ rpm: rpmcommon
--define "upstream_version $(VERSION)" \
--define "rpmversion $(RPMVERSION)" \
--define "rpmrelease $(RPMRELEASE)" \
- --define "repotag $(REPOTAG)" \
+ $(EXTRA_RPM_DEFINES) \
-ba rpm-build/$(NAME).spec
@rm -f rpm-build/$(NAME).spec
@echo "#############################################"
diff --git a/PKG-INFO b/PKG-INFO
index 4868579..29cc1f6 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,116 @@
Metadata-Version: 2.1
Name: ansible
-Version: 2.5.0
+Version: 2.5.1
Summary: Radically simple IT automation
Home-page: https://ansible.com/
Author: Ansible, Inc.
Author-email: info@ansible.com
License: GPLv3+
-Description: UNKNOWN
+Project-URL: Bug Tracker, https://github.com/ansible/ansible/issues
+Project-URL: Documentation, https://docs.ansible.com/ansible/
+Project-URL: CI: Shippable, https://app.shippable.com/github/ansible/ansible
+Project-URL: Source Code, https://github.com/ansible/ansible
+Description: |PyPI version| |Docs badge| |Build Status|
+
+ *******
+ Ansible
+ *******
+
+ Ansible is a radically simple IT automation system. It handles
+ configuration-management, application deployment, cloud provisioning,
+ ad-hoc task-execution, and multinode orchestration -- including
+ trivializing things like zero-downtime rolling updates with load
+ balancers.
+
+ Read the documentation and more at https://ansible.com/
+
+ You can find installation instructions
+ `here <https://docs.ansible.com/intro_getting_started.html>`_ for a
+ variety of platforms.
+
+ Most users should probably install a released version of Ansible from ``pip``, a package manager or
+ our `release repository <https://releases.ansible.com/ansible/>`_. `Officially supported
+ <https://www.ansible.com/ansible-engine>`_ builds of Ansible are also available. Some power users
+ run directly from the development branch - while significant efforts are made to ensure that
+ ``devel`` is reasonably stable, you're more likely to encounter breaking changes when running
+ Ansible this way.
+
+ Design Principles
+ =================
+
+ * Have a dead simple setup process and a minimal learning curve
+ * Manage machines very quickly and in parallel
+ * Avoid custom-agents and additional open ports, be agentless by
+ leveraging the existing SSH daemon
+ * Describe infrastructure in a language that is both machine and human
+ friendly
+ * Focus on security and easy auditability/review/rewriting of content
+ * Manage new remote machines instantly, without bootstrapping any
+ software
+ * Allow module development in any dynamic language, not just Python
+ * Be usable as non-root
+ * Be the easiest IT automation system to use, ever.
+
+ Get Involved
+ ============
+
+ * Read `Community
+ Information <https://docs.ansible.com/community.html>`_ for all
+ kinds of ways to contribute to and interact with the project,
+ including mailing list information and how to submit bug reports and
+ code to Ansible.
+ * All code submissions are done through pull requests. Take care to
+ make sure no merge commits are in the submission, and use
+ ``git rebase`` vs ``git merge`` for this reason. If submitting a
+ large code change (other than modules), it's probably a good idea to
+ join ansible-devel and talk about what you would like to do or add
+ first to avoid duplicate efforts. This not only helps everyone
+ know what's going on, it also helps save time and effort if we decide
+ some changes are needed.
+ * Users list:
+ `ansible-project <https://groups.google.com/group/ansible-project>`_
+ * Development list:
+ `ansible-devel <https://groups.google.com/group/ansible-devel>`_
+ * Announcement list:
+ `ansible-announce <https://groups.google.com/group/ansible-announce>`_
+ -- read only
+ * irc.freenode.net: #ansible
+
+ Branch Info
+ ===========
+
+ * Releases are named after Led Zeppelin songs. (Releases prior to 2.0
+ were named after Van Halen songs.)
+ * The devel branch corresponds to the release actively under
+ development.
+ * Various release-X.Y branches exist for previous releases.
+ * We'd love to have your contributions, read `Community
+ Information <https://docs.ansible.com/community.html>`_ for notes on
+ how to get started.
+
+ Authors
+ =======
+
+ Ansible was created by `Michael DeHaan <https://github.com/mpdehaan>`_
+ (michael.dehaan/gmail/com) and has contributions from over 1000 users
+ (and growing). Thanks everyone!
+
+ Ansible is sponsored by `Ansible, Inc <https://ansible.com>`_
+
+ License
+ =======
+
+ GNU General Public License v3.0
+
+ See `COPYING <COPYING>`_ to see the full text.
+
+ .. |PyPI version| image:: https://img.shields.io/pypi/v/ansible.svg
+ :target: https://pypi.org/project/ansible
+ .. |Docs badge| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg
+ :target: http://docs.ansible.com/ansible
+ .. |Build Status| image:: https://api.shippable.com/projects/573f79d02a8192902e20e34b/badge?branch=devel
+ :target: https://app.shippable.com/projects/573f79d02a8192902e20e34b
+
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
@@ -18,7 +122,11 @@ Classifier: Natural Language :: English
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
Classifier: Topic :: System :: Installation/Setup
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
+Requires-Python: >=2.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*
Provides-Extra: azure
diff --git a/README.md b/README.md
deleted file mode 100644
index 30f6a01..0000000
--- a/README.md
+++ /dev/null
@@ -1,56 +0,0 @@
-[![PyPI version](https://img.shields.io/pypi/v/ansible.svg)](https://pypi.python.org/pypi/ansible)
-[![Build Status](https://api.shippable.com/projects/573f79d02a8192902e20e34b/badge?branch=devel)](https://app.shippable.com/projects/573f79d02a8192902e20e34b)
-
-
-Ansible
-=======
-
-Ansible is a radically simple IT automation system. It handles configuration-management, application deployment, cloud provisioning, ad-hoc task-execution, and multinode orchestration - including trivializing things like zero-downtime rolling updates with load balancers.
-
-Read the documentation and more at https://ansible.com/
-
-You can find installation instructions [here](https://docs.ansible.com/ansible/intro_installation.html) for a variety of platforms. Most users should probably install a released version of Ansible from `pip`, a package manager or our [release repository](https://releases.ansible.com/ansible/). [Officially supported](https://www.ansible.com/ansible-engine) builds of Ansible are also available. Some power users run directly from the development branch - while significant efforts are made to ensure that `devel` is reasonably stable, you're more likely to encounter breaking changes when running Ansible this way.
-
-Design Principles
-=================
-
- * Have a dead simple setup process and a minimal learning curve
- * Manage machines very quickly and in parallel
- * Avoid custom-agents and additional open ports, be agentless by leveraging the existing SSH daemon
- * Describe infrastructure in a language that is both machine and human friendly
- * Focus on security and easy auditability/review/rewriting of content
- * Manage new remote machines instantly, without bootstrapping any software
- * Allow module development in any dynamic language, not just Python
- * Be usable as non-root
- * Be the easiest IT automation system to use, ever.
-
-Get Involved
-============
-
- * Read [Community Information](https://docs.ansible.com/community.html) for all kinds of ways to contribute to and interact with the project, including mailing list information and how to submit bug reports and code to Ansible.
- * All code submissions are done through pull requests. Take care to make sure no merge commits are in the submission, and use `git rebase` vs `git merge` for this reason. If submitting a large code change (other than modules), it's probably a good idea to join ansible-devel and talk about what you would like to do or add first and to avoid duplicate efforts. This not only helps everyone know what's going on, it also helps save time and effort if we decide some changes are needed.
- * Users list: [ansible-project](https://groups.google.com/group/ansible-project)
- * Development list: [ansible-devel](https://groups.google.com/group/ansible-devel)
- * Announcement list: [ansible-announce](https://groups.google.com/group/ansible-announce) - read only
- * irc.freenode.net: #ansible
-
-Branch Info
-===========
-
- * Releases are named after Led Zeppelin songs. (Releases prior to 2.0 were named after Van Halen songs.)
- * The devel branch corresponds to the release actively under development.
- * Various release-X.Y branches exist for previous releases.
- * We'd love to have your contributions, read [Community Information](https://docs.ansible.com/community.html) for notes on how to get started.
-
-Authors
-=======
-
-Ansible was created by [Michael DeHaan](https://github.com/mpdehaan) (michael.dehaan/gmail/com) and has contributions from over 1000 users (and growing). Thanks everyone!
-
-Ansible is sponsored by [Ansible, Inc](https://ansible.com)
-
-License
-=======
-GNU General Public License v3.0
-
-See [COPYING](COPYING) to see the full text.
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..a142d96
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,100 @@
+|PyPI version| |Docs badge| |Build Status|
+
+*******
+Ansible
+*******
+
+Ansible is a radically simple IT automation system. It handles
+configuration-management, application deployment, cloud provisioning,
+ad-hoc task-execution, and multinode orchestration -- including
+trivializing things like zero-downtime rolling updates with load
+balancers.
+
+Read the documentation and more at https://ansible.com/
+
+You can find installation instructions
+`here <https://docs.ansible.com/intro_getting_started.html>`_ for a
+variety of platforms.
+
+Most users should probably install a released version of Ansible from ``pip``, a package manager or
+our `release repository <https://releases.ansible.com/ansible/>`_. `Officially supported
+<https://www.ansible.com/ansible-engine>`_ builds of Ansible are also available. Some power users
+run directly from the development branch - while significant efforts are made to ensure that
+``devel`` is reasonably stable, you're more likely to encounter breaking changes when running
+Ansible this way.
+
+Design Principles
+=================
+
+* Have a dead simple setup process and a minimal learning curve
+* Manage machines very quickly and in parallel
+* Avoid custom-agents and additional open ports, be agentless by
+ leveraging the existing SSH daemon
+* Describe infrastructure in a language that is both machine and human
+ friendly
+* Focus on security and easy auditability/review/rewriting of content
+* Manage new remote machines instantly, without bootstrapping any
+ software
+* Allow module development in any dynamic language, not just Python
+* Be usable as non-root
+* Be the easiest IT automation system to use, ever.
+
+Get Involved
+============
+
+* Read `Community
+ Information <https://docs.ansible.com/community.html>`_ for all
+ kinds of ways to contribute to and interact with the project,
+ including mailing list information and how to submit bug reports and
+ code to Ansible.
+* All code submissions are done through pull requests. Take care to
+ make sure no merge commits are in the submission, and use
+ ``git rebase`` vs ``git merge`` for this reason. If submitting a
+ large code change (other than modules), it's probably a good idea to
+ join ansible-devel and talk about what you would like to do or add
+ first to avoid duplicate efforts. This not only helps everyone
+ know what's going on, it also helps save time and effort if we decide
+ some changes are needed.
+* Users list:
+ `ansible-project <https://groups.google.com/group/ansible-project>`_
+* Development list:
+ `ansible-devel <https://groups.google.com/group/ansible-devel>`_
+* Announcement list:
+ `ansible-announce <https://groups.google.com/group/ansible-announce>`_
+ -- read only
+* irc.freenode.net: #ansible
+
+Branch Info
+===========
+
+* Releases are named after Led Zeppelin songs. (Releases prior to 2.0
+ were named after Van Halen songs.)
+* The devel branch corresponds to the release actively under
+ development.
+* Various release-X.Y branches exist for previous releases.
+* We'd love to have your contributions, read `Community
+ Information <https://docs.ansible.com/community.html>`_ for notes on
+ how to get started.
+
+Authors
+=======
+
+Ansible was created by `Michael DeHaan <https://github.com/mpdehaan>`_
+(michael.dehaan/gmail/com) and has contributions from over 1000 users
+(and growing). Thanks everyone!
+
+Ansible is sponsored by `Ansible, Inc <https://ansible.com>`_
+
+License
+=======
+
+GNU General Public License v3.0
+
+See `COPYING <COPYING>`_ to see the full text.
+
+.. |PyPI version| image:: https://img.shields.io/pypi/v/ansible.svg
+ :target: https://pypi.org/project/ansible
+.. |Docs badge| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg
+ :target: http://docs.ansible.com/ansible
+.. |Build Status| image:: https://api.shippable.com/projects/573f79d02a8192902e20e34b/badge?branch=devel
+ :target: https://app.shippable.com/projects/573f79d02a8192902e20e34b
diff --git a/changelogs/CHANGELOG-v2.5.rst b/changelogs/CHANGELOG-v2.5.rst
index 61e7ea8..500f042 100644
--- a/changelogs/CHANGELOG-v2.5.rst
+++ b/changelogs/CHANGELOG-v2.5.rst
@@ -2,16 +2,213 @@
Ansible 2.5 "Kashmir" Release Notes
===================================
-v2.5.0
+v2.5.1
======
Release Summary
---------------
-| Release Date: 2018-03-22
+| Release Date: 2018-04-18
| `Porting Guide <https://docs.ansible.com/ansible/devel/porting_guides.html>`_
+Minor Changes
+-------------
+
+- Updated example in vcenter_license module.
+
+- Updated virtual machine facts with instanceUUID which is unique for each VM irrespective of name and BIOS UUID.
+
+
+Bugfixes
+--------
+
+- EOS can not check configuration without use of config session (ANSIBLE_EOS_USE_SESSIONS=0). Fix is to throw error when hiting into this exception case. Configs would neither be checked nor be played on the eos device.
+
+- Adds exception handling which is raised when user does not have correct set of permissions/privileges to read virtual machine facts.
+
+- onyx_pfc_interface - Add support for changes in pfc output in onyx 3.6.6000 https://github.com/ansible/ansible/pull/37651
+
+- Fix mlag summary json parsing for onyx version 3.6.6000 and above https://github.com/ansible/ansible/pull/38191
+
+- Update documentation related to datacenter in vmware_guest_find module. Mark datacenter as optional.
+
+- Set default network type as 'dhcp' if user has not specified any.
+
+- nmcli change default value of autoconnect
+
+- azure_rm_image - Allow Azure images to be created with tags, bug was introduced in Ansible v2.5.0
+
+- azure_rm_networkinterface - fixed examples in module documentation and added fix to allow an IP configuration with no public IP (https://github.com/ansible/ansible/pull/36824)
+
+- azure_rm_virtualmachine - removed docs note that says on marketplace images can be used, custom images were added in 2.5
+
+- Improve keyed groups for complex inventory
+
+- Made separator configurable
+
+- Fixed some exception types
+
+- Better error messages
+
+- backup options doc change to reflect backup directory location in case playbook is run from a role
+
+- filters - Don't overwrite builtin jinja2 filters with tests (https://github.com/ansible/ansible/pull/37881)
+
+- edgeos_command - add action plugin to backup config (https://github.com/ansible/ansible/pull/37619)
+
+- eos_vlan - fixed eos_vlan not working when having more than 6 interfaces (https://github.com/ansible/ansible/pull/38347)
+
+- Various grafana_* modules - Port away from the deprecated b64encodestring function to the b64encode function instead. (https://github.com/ansible/ansible/pull/38388)
+
+- include_role - Fix parameter templating (https://github.com/ansible/ansible/pull/36372)
+
+- include_vars - Call DataLoader.load with the correct signature to prevent hang on error processing (https://github.com/ansible/ansible/pull/38194)
+
+- ios_interface - neighbors option now include CDP neighbors (https://github.com/ansible/ansible/pull/37667)
+
+- ios_l2_interface - fix removal of trunk vlans (https://github.com/ansible/ansible/pull/37389)
+
+- Add supported connection in junos module documentation (https://github.com/ansible/ansible/pull/38813)
+
+- _nxos_switchport - fix removal of trunk vlans (https://github.com/ansible/ansible/pull/37328)
+
+- nxos_l2_interface - fix removal of trunk vlans (https://github.com/ansible/ansible/pull/37336)
+
+- nxos_snapshot - fix documentation and add required parameter logic (https://github.com/ansible/ansible/pull/37232, https://github.com/ansible/ansible/pull/37248)
+
+- Improve integration test - Ensure each transport test runs only once (https://github.com/ansible/ansible/pull/37462)
+
+- nxos_user - Integration test (https://github.com/ansible/ansible/pull/37852)
+
+- nxos_bgp_af - Fix UnboundLocalError (https://github.com/ansible/ansible/pull/37610)
+
+- nxos_vrf - Fix nxos_vrf issues (https://github.com/ansible/ansible/pull/37092)
+
+- nxos_vrf_af - Fix nxos_vrf_af issues (https://github.com/ansible/ansible/pull/37211)
+
+- nxos_udld - Fix nxos_udld issues (https://github.com/ansible/ansible/pull/37418)
+
+- nxos_vlan - Fix nxos_vlan issues (https://github.com/ansible/ansible/pull/38008)
+
+- nxos_vlan - nxos_vlan purge (https://github.com/ansible/ansible/pull/38202)
+
+- nxos_aaa_server - Fix nxos_aaa_server (https://github.com/ansible/ansible/pull/38117)
+
+- nxos_aaa_server_host - Fix nxos_aaa_server_host (https://github.com/ansible/ansible/pull/38188)
+
+- nxos_acl - Fix nxos_acl (https://github.com/ansible/ansible/pull/38283)
+
+- nxos_static_route - Fix nxos_static_route (https://github.com/ansible/ansible/pull/37614)
+
+- nxos_acl_interface test - Fix nxos_acl_interface test (https://github.com/ansible/ansible/pull/38230)
+
+- nxos_igmp - Fix nxos_igmp (https://github.com/ansible/ansible/pull/38496)
+
+- nxos_hsrp - Fix nxos_hsrp (https://github.com/ansible/ansible/pull/38410)
+
+- nxos_igmp_snooping - Fix nxos_igmp_snooping (https://github.com/ansible/ansible/pull/38566)
+
+- nxos_ntp_auth - Fix nxos_ntp_auth issues (https://github.com/ansible/ansible/pull/38824)
+
+- nxos_ntp_options - Fix nxos_ntp_options issues (https://github.com/ansible/ansible/pull/38695)
+
+- Fix onyx_config action plugin when used on Python 3 https://github.com/ansible/ansible/pull/38343
+
+- openssl_certificate - Handle dump() in check_mode https://github.com/ansible/ansible/pull/38386
+
+- Fix traceback when creating or stopping ovirt vms (https://github.com/ansible/ansible/pull/37249)
+
+- Fix for consul_kv idempotence on Python3 https://github.com/ansible/ansible/issues/35893
+
+- Fix csvfile lookup plugin when used on Python3 https://github.com/ansible/ansible/pull/37625
+
+- ec2 - Fix ec2 user_data parameter to properly convert to base64 on python3 (https://github.com/ansible/ansible/pull/37628)
+
+- Fix to send and receive bytes over a socket in the haproxy module which was causing tracebacks on Python3 https://github.com/ansible/ansible/pull/35176
+
+- jira module - Fix bytes/text handling for base64 encoding authentication tokens (https://github.com/ansible/ansible/pull/33862)
+
+- ansible-pull - fixed a bug checking for changes when we've pulled from the git repository on python3 https://github.com/ansible/ansible/issues/36962
+
+- Fix bytes/text handling in vagrant dynamic inventory https://github.com/ansible/ansible/pull/37631
+
+- wait_for_connection - Fix python3 compatibility bug (https://github.com/ansible/ansible/pull/37646)
+
+- restore stderr ouput even if script module run is successful (https://github.com/ansible/ansible/pull/38177)
+
+- ec2_asg - no longer terminates an instance before creating a replacement (https://github.com/ansible/ansible/pull/36679)
+
+- ec2_group - security groups in default VPCs now have a default egress rule (https://github.com/ansible/ansible/pull/38018)
+
+- inventory correctly removes hosts from 'ungrouped' group (https://github.com/ansible/ansible/pull/37617)
+
+- letsencrypt - fixed domain matching authorization (https://github.com/ansible/ansible/pull/37558)
+
+- letsencrypt - improved elliptic curve account key parsing (https://github.com/ansible/ansible/pull/37275)
+
+- facts are no longer processed more than once for each action (https://github.com/ansible/ansible/issues/37535)
+
+- cs_vpc_offering - only return VPC offferings matching name arg (https://github.com/ansible/ansible/pull/37783)
+
+- cs_configuration - filter names inside the module instead of relying on API (https://github.com/ansible/ansible/pull/37910)
+
+- various fixes to networking module connection subsystem (https://github.com/ansible/ansible/pull/37529)
+
+- ios_* - fixed netconf issues (https://github.com/ansible/ansible/pull/38155)
+
+- ovirt_* - various bugfixes (https://github.com/ansible/ansible/pull/38341)
+
+- ansible-vault no longer requires '--encrypt-vault-id' with edit (https://github.com/ansible/ansible/pull/35923)
+
+- k8s lookup plugin now uses same auth method as other k8s modules (https://github.com/ansible/ansible/pull/37533)
+
+- ansible-inventory now properly displays group_var graph (https://github.com/ansible/ansible/pull/38744)
+
+- setup - FreeBSD fact gathering no longer fails on missing dmesg, sysctl, etc (https://github.com/ansible/ansible/pull/37194)
+
+- inventory scripts now read passwords without byte interpolation (https://github.com/ansible/ansible/pull/35582)
+
+- user - fixed password expiration support in FreeBSD
+
+- meta - inventory_refresh now works properly on YAML inventory plugins (https://github.com/ansible/ansible/pull/38242)
+
+- foreman callback plugin - fixed API options (https://github.com/ansible/ansible/pull/38138)
+
+- win_certificate_store - fixed a typo that stopped it from getting the key_storage values
+
+- 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)
+
+- Ansible.ModuleUtils.FileUtil - Catch DirectoryNotFoundException with Test-AnsiblePath (https://github.com/ansible/ansible/pull/37968)
+
+- win_exec_wrapper - support loading of Windows modules different different line endings than the core modules (https://github.com/ansible/ansible/pull/37291)
+
+- win_reboot - fix deprecated warning message to show version in correct spot (https://github.com/ansible/ansible/pull/37898)
+
+- win_regedit - wait for garbage collection to finish before trying to unload the hive in case handles didn't unload in time (https://github.com/ansible/ansible/pull/38912)
+
+- win_service - Fix bug with win_service not being able to handle special chars like '[' (https://github.com/ansible/ansible/pull/37897)
+
+- win_setup - Use connection name for network interfaces as interface name isn't helpful (https://github.com/ansible/ansible/pull/37327)
+
+- win_setup - fix bug where getting the machine SID would take a long time in large domain environments (https://github.com/ansible/ansible/pull/38646)
+
+- win_updates - handle if the module fails to load and return the error message (https://github.com/ansible/ansible/pull/38363)
+
+- win_uri - do not override existing header when using the ``headers`` key. (https://github.com/ansible/ansible/pull/37845)
+
+- win_uri - convert status code values to an int before validating them in server response (https://github.com/ansible/ansible/pull/38080)
+
+- windows - display UTF-8 characters correctly in Windows return json (https://github.com/ansible/ansible/pull/37229)
+
+- winrm - when managing Kerberos tickets in Ansible, get a forwardable ticket if delegation is set (https://github.com/ansible/ansible/pull/37815)
+
+
+v2.5.0
+======
+
Major Changes
-------------
@@ -642,7 +839,7 @@ Bugfixes
- znode - fixed a bug calling the zookeeper API under Python3 https://github.com/ansible/ansible/pull/36999
-- Fix for unarchive when users use the --strip-components extra_opt to tar causing ansible to set permissions on the wrong directory. https://github.com/ansible/ansible/pull/37048
+- Fix for unarchive when users use the --strip-components extra_opt to tar causing ansible to set permissions on the wrong directory. (https://github.com/ansible/ansible/pull/37048)
- fixed templating issues in loop_control (https://github.com/ansible/ansible/pull/36124)
diff --git a/contrib/inventory/cloudforms.py b/contrib/inventory/cloudforms.py
index 247d297..c6702dc 100755
--- a/contrib/inventory/cloudforms.py
+++ b/contrib/inventory/cloudforms.py
@@ -138,7 +138,7 @@ class CloudFormsInventory(object):
warnings.warn("No username specified, you need to specify a CloudForms username.")
if config.has_option('cloudforms', 'password'):
- self.cloudforms_pw = config.get('cloudforms', 'password')
+ self.cloudforms_pw = config.get('cloudforms', 'password', raw=True)
else:
self.cloudforms_pw = None
diff --git a/contrib/inventory/foreman.py b/contrib/inventory/foreman.py
index 4d6fd32..e1ca878 100755
--- a/contrib/inventory/foreman.py
+++ b/contrib/inventory/foreman.py
@@ -84,7 +84,7 @@ class ForemanInventory(object):
try:
self.foreman_url = config.get('foreman', 'url')
self.foreman_user = config.get('foreman', 'user')
- self.foreman_pw = config.get('foreman', 'password')
+ self.foreman_pw = config.get('foreman', 'password', raw=True)
self.foreman_ssl_verify = config.getboolean('foreman', 'ssl_verify')
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError) as e:
print("Error parsing configuration: %s" % e, file=sys.stderr)
diff --git a/contrib/inventory/ovirt4.py b/contrib/inventory/ovirt4.py
index 5349922..cf2f7ad 100755
--- a/contrib/inventory/ovirt4.py
+++ b/contrib/inventory/ovirt4.py
@@ -138,7 +138,7 @@ def create_connection():
return sdk.Connection(
url=config.get('ovirt', 'ovirt_url'),
username=config.get('ovirt', 'ovirt_username'),
- password=config.get('ovirt', 'ovirt_password'),
+ password=config.get('ovirt', 'ovirt_password', raw=True),
ca_file=config.get('ovirt', 'ovirt_ca_file'),
insecure=config.get('ovirt', 'ovirt_ca_file') is None,
)
diff --git a/contrib/inventory/vagrant.py b/contrib/inventory/vagrant.py
index c6e0ff3..0723400 100755
--- a/contrib/inventory/vagrant.py
+++ b/contrib/inventory/vagrant.py
@@ -38,14 +38,17 @@ import os.path
import subprocess
import re
from paramiko import SSHConfig
-from cStringIO import StringIO
from optparse import OptionParser
from collections import defaultdict
try:
import json
-except:
+except Exception:
import simplejson as json
+from ansible.module_utils._text import to_text
+from ansible.module_utils.six.moves import StringIO
+
+
_group = 'vagrant' # a default group
_ssh_to_ansible = [('user', 'ansible_ssh_user'),
('hostname', 'ansible_ssh_host'),
@@ -74,7 +77,8 @@ def get_ssh_config():
# list all the running boxes
def list_running_boxes():
- output = subprocess.check_output(["vagrant", "status"]).split('\n')
+
+ output = to_text(subprocess.check_output(["vagrant", "status"]), errors='surrogate_or_strict').split('\n')
boxes = []
@@ -90,7 +94,7 @@ def list_running_boxes():
def get_a_ssh_config(box_name):
"""Gives back a map of all the machine's ssh configurations"""
- output = subprocess.check_output(["vagrant", "ssh-config", box_name])
+ output = to_text(subprocess.check_output(["vagrant", "ssh-config", box_name]), errors='surrogate_or_strict')
config = SSHConfig()
config.parse(StringIO(output))
host_config = config.lookup(box_name)
@@ -104,6 +108,7 @@ def get_a_ssh_config(box_name):
return dict((v, host_config[k]) for k, v in _ssh_to_ansible)
+
# List out servers that vagrant has running
# ------------------------------
if options.list:
diff --git a/contrib/inventory/vmware_inventory.py b/contrib/inventory/vmware_inventory.py
index 7f3537b..997f53d 100755
--- a/contrib/inventory/vmware_inventory.py
+++ b/contrib/inventory/vmware_inventory.py
@@ -268,7 +268,7 @@ class VMWareInventory(object):
self.port = int(os.environ.get('VMWARE_PORT', config.get('vmware', 'port')))
self.username = os.environ.get('VMWARE_USERNAME', config.get('vmware', 'username'))
self.debugl('username is %s' % self.username)
- self.password = os.environ.get('VMWARE_PASSWORD', config.get('vmware', 'password'))
+ self.password = os.environ.get('VMWARE_PASSWORD', config.get('vmware', 'password', raw=True))
self.validate_certs = os.environ.get('VMWARE_VALIDATE_CERTS', config.get('vmware', 'validate_certs'))
if self.validate_certs in ['no', 'false', 'False', False]:
self.validate_certs = False
diff --git a/docs/bin/plugin_formatter.py b/docs/bin/plugin_formatter.py
index 12a034f..6f1152d 100755
--- a/docs/bin/plugin_formatter.py
+++ b/docs/bin/plugin_formatter.py
@@ -560,6 +560,7 @@ These modules are currently shipped with Ansible, but will most likely be shippe
'modules': data['modules'],
'slug': data['slug'],
'module_info': plugin_info,
+ 'plugin_type': plugin_type
}
text = templates['support_list'].render(template_data)
write_data(text, output_dir, data['output'])
diff --git a/docs/bin/testing_formatter.sh b/docs/bin/testing_formatter.sh
index 926bde9..9742319 100755
--- a/docs/bin/testing_formatter.sh
+++ b/docs/bin/testing_formatter.sh
@@ -4,9 +4,13 @@ cat <<- EOF > ../docsite/rst/dev_guide/testing/sanity/index.rst
Sanity Tests
============
-The following sanity tests are available as \`\`--test\`\` options for \`\`ansible-test sanity\`\`:
+The following sanity tests are available as \`\`--test\`\` options for \`\`ansible-test sanity\`\`.
+This list is also available using \`\`ansible-test sanity --list-tests\`\`.
+
+.. toctree::
+ :maxdepth: 1
+
+$(for test in $(../../test/runner/ansible-test sanity --list-tests); do echo " ${test}"; done)
-$(for test in $(../../test/runner/ansible-test sanity --list-tests); do echo "- :doc:\`${test} <${test}>\`"; done)
-This list is also available using \`\`ansible-test sanity --list-tests\`\`.
EOF
diff --git a/docs/man/man1/ansible-config.1 b/docs/man/man1/ansible-config.1
index 8fdf543..82d7f85 100644
--- a/docs/man/man1/ansible-config.1
+++ b/docs/man/man1/ansible-config.1
@@ -2,12 +2,12 @@
.\" Title: ansible-config
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
-.\" Date: 03/23/2018
+.\" Date: 04/19/2018
.\" Manual: System administration commands
-.\" Source: Ansible 2.5.0
+.\" Source: Ansible 2.5.1
.\" Language: English
.\"
-.TH "ANSIBLE\-CONFIG" "1" "03/23/2018" "Ansible 2\&.5\&.0" "System administration commands"
+.TH "ANSIBLE\-CONFIG" "1" "04/19/2018" "Ansible 2\&.5\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
diff --git a/docs/man/man1/ansible-console.1 b/docs/man/man1/ansible-console.1
index e01f672..8d801a9 100644
--- a/docs/man/man1/ansible-console.1
+++ b/docs/man/man1/ansible-console.1
@@ -2,12 +2,12 @@
.\" Title: ansible-console
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
-.\" Date: 03/23/2018
+.\" Date: 04/19/2018
.\" Manual: System administration commands
-.\" Source: Ansible 2.5.0
+.\" Source: Ansible 2.5.1
.\" Language: English
.\"
-.TH "ANSIBLE\-CONSOLE" "1" "03/23/2018" "Ansible 2\&.5\&.0" "System administration commands"
+.TH "ANSIBLE\-CONSOLE" "1" "04/19/2018" "Ansible 2\&.5\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
diff --git a/docs/man/man1/ansible-doc.1 b/docs/man/man1/ansible-doc.1
index ac389e3..70bb841 100644
--- a/docs/man/man1/ansible-doc.1
+++ b/docs/man/man1/ansible-doc.1
@@ -2,12 +2,12 @@
.\" Title: ansible-doc
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
-.\" Date: 03/23/2018
+.\" Date: 04/19/2018
.\" Manual: System administration commands
-.\" Source: Ansible 2.5.0
+.\" Source: Ansible 2.5.1
.\" Language: English
.\"
-.TH "ANSIBLE\-DOC" "1" "03/23/2018" "Ansible 2\&.5\&.0" "System administration commands"
+.TH "ANSIBLE\-DOC" "1" "04/19/2018" "Ansible 2\&.5\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
diff --git a/docs/man/man1/ansible-galaxy.1 b/docs/man/man1/ansible-galaxy.1
index 6226c44..513bf7e 100644
--- a/docs/man/man1/ansible-galaxy.1
+++ b/docs/man/man1/ansible-galaxy.1
@@ -2,12 +2,12 @@
.\" Title: ansible-galaxy
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
-.\" Date: 03/23/2018
+.\" Date: 04/19/2018
.\" Manual: System administration commands
-.\" Source: Ansible 2.5.0
+.\" Source: Ansible 2.5.1
.\" Language: English
.\"
-.TH "ANSIBLE\-GALAXY" "1" "03/23/2018" "Ansible 2\&.5\&.0" "System administration commands"
+.TH "ANSIBLE\-GALAXY" "1" "04/19/2018" "Ansible 2\&.5\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
diff --git a/docs/man/man1/ansible-inventory.1 b/docs/man/man1/ansible-inventory.1
index a0757ea..d2b4983 100644
--- a/docs/man/man1/ansible-inventory.1
+++ b/docs/man/man1/ansible-inventory.1
@@ -2,12 +2,12 @@
.\" Title: ansible-inventory
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
-.\" Date: 03/23/2018
+.\" Date: 04/19/2018
.\" Manual: System administration commands
-.\" Source: Ansible 2.5.0
+.\" Source: Ansible 2.5.1
.\" Language: English
.\"
-.TH "ANSIBLE\-INVENTORY" "1" "03/23/2018" "Ansible 2\&.5\&.0" "System administration commands"
+.TH "ANSIBLE\-INVENTORY" "1" "04/19/2018" "Ansible 2\&.5\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@@ -62,11 +62,6 @@ Output specific host info, works as inventory script
Output all hosts info, works as inventory script
.RE
.PP
-\fB\-\-list\-hosts\fR
-.RS 4
-outputs a list of matching hosts; does not execute anything else
-.RE
-.PP
\fB\-\-playbook\-dir\fR \fIBASEDIR\fR
.RS 4
Since this tool does not use playbooks, use this as a subsitute playbook directory\&.This sets the relative path for many features including roles/ group_vars/ etc\&.
@@ -102,11 +97,6 @@ show this help message and exit
specify inventory host path or comma separated host list\&. \-\-inventory\-file is deprecated
.RE
.PP
-\fB\-l\fR \fISUBSET\fR, \fB\-\-limit\fR \fISUBSET\fR
-.RS 4
-further limit selected hosts to an additional pattern
-.RE
-.PP
\fB\-v\fR, \fB\-\-verbose\fR
.RS 4
verbose mode (\-vvv for more, \-vvvv to enable connection debugging)
diff --git a/docs/man/man1/ansible-inventory.1.asciidoc.in b/docs/man/man1/ansible-inventory.1.asciidoc.in
index cd6783e..738fa4e 100644
--- a/docs/man/man1/ansible-inventory.1.asciidoc.in
+++ b/docs/man/man1/ansible-inventory.1.asciidoc.in
@@ -45,10 +45,6 @@ Output specific host info, works as inventory script
Output all hosts info, works as inventory script
-*--list-hosts*::
-
-outputs a list of matching hosts; does not execute anything else
-
*--playbook-dir* 'BASEDIR'::
Since this tool does not use playbooks, use this as a subsitute playbook directory.This sets the relative path for many features including roles/ group_vars/ etc.
@@ -77,10 +73,6 @@ show this help message and exit
specify inventory host path or comma separated host list. --inventory-file is deprecated
-*-l* 'SUBSET', *--limit* 'SUBSET'::
-
-further limit selected hosts to an additional pattern
-
*-v*, *--verbose*::
verbose mode (-vvv for more, -vvvv to enable connection debugging)
diff --git a/docs/man/man1/ansible-playbook.1 b/docs/man/man1/ansible-playbook.1
index 269db95..3f7e70c 100644
--- a/docs/man/man1/ansible-playbook.1
+++ b/docs/man/man1/ansible-playbook.1
@@ -2,12 +2,12 @@
.\" Title: ansible-playbook
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
-.\" Date: 03/23/2018
+.\" Date: 04/19/2018
.\" Manual: System administration commands
-.\" Source: Ansible 2.5.0
+.\" Source: Ansible 2.5.1
.\" Language: English
.\"
-.TH "ANSIBLE\-PLAYBOOK" "1" "03/23/2018" "Ansible 2\&.5\&.0" "System administration commands"
+.TH "ANSIBLE\-PLAYBOOK" "1" "04/19/2018" "Ansible 2\&.5\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
diff --git a/docs/man/man1/ansible-pull.1 b/docs/man/man1/ansible-pull.1
index e2dc7d1..412bf2b 100644
--- a/docs/man/man1/ansible-pull.1
+++ b/docs/man/man1/ansible-pull.1
@@ -2,12 +2,12 @@
.\" Title: ansible-pull
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
-.\" Date: 03/23/2018
+.\" Date: 04/19/2018
.\" Manual: System administration commands
-.\" Source: Ansible 2.5.0
+.\" Source: Ansible 2.5.1
.\" Language: English
.\"
-.TH "ANSIBLE\-PULL" "1" "03/23/2018" "Ansible 2\&.5\&.0" "System administration commands"
+.TH "ANSIBLE\-PULL" "1" "04/19/2018" "Ansible 2\&.5\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
diff --git a/docs/man/man1/ansible-vault.1 b/docs/man/man1/ansible-vault.1
index 28109a8..b7e0a00 100644
--- a/docs/man/man1/ansible-vault.1
+++ b/docs/man/man1/ansible-vault.1
@@ -2,12 +2,12 @@
.\" Title: ansible-vault
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
-.\" Date: 03/23/2018
+.\" Date: 04/19/2018
.\" Manual: System administration commands
-.\" Source: Ansible 2.5.0
+.\" Source: Ansible 2.5.1
.\" Language: English
.\"
-.TH "ANSIBLE\-VAULT" "1" "03/23/2018" "Ansible 2\&.5\&.0" "System administration commands"
+.TH "ANSIBLE\-VAULT" "1" "04/19/2018" "Ansible 2\&.5\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@@ -140,6 +140,11 @@ Prompt for the string to encrypt
\fBedit\fR
.RS 4
open and decrypt an existing vaulted file in an editor, that will be encryped again when closed
+.PP
+\fB\-\-encrypt\-vault\-id\fR \fIENCRYPT_VAULT_ID\fR
+.RS 4
+the vault id used to encrypt (required if more than vault\-id is provided)
+.RE
.RE
.PP
\fBcreate\fR
diff --git a/docs/man/man1/ansible-vault.1.asciidoc.in b/docs/man/man1/ansible-vault.1.asciidoc.in
index 32a0b6f..8ef7880 100644
--- a/docs/man/man1/ansible-vault.1.asciidoc.in
+++ b/docs/man/man1/ansible-vault.1.asciidoc.in
@@ -137,6 +137,12 @@ again when closed
+*--encrypt-vault-id* 'ENCRYPT_VAULT_ID'::
+
+ the vault id used to encrypt (required if more than vault-id is provided)
+
+
+
*create*::: create and open a file in an editor that will be encryped with the provided
vault secret when closed
diff --git a/docs/man/man1/ansible.1 b/docs/man/man1/ansible.1
index dcbb496..fa8a077 100644
--- a/docs/man/man1/ansible.1
+++ b/docs/man/man1/ansible.1
@@ -2,12 +2,12 @@
.\" Title: ansible
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
-.\" Date: 03/23/2018
+.\" Date: 04/19/2018
.\" Manual: System administration commands
-.\" Source: Ansible 2.5.0
+.\" Source: Ansible 2.5.1
.\" Language: English
.\"
-.TH "ANSIBLE" "1" "03/23/2018" "Ansible 2\&.5\&.0" "System administration commands"
+.TH "ANSIBLE" "1" "04/19/2018" "Ansible 2\&.5\&.1" "System administration commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
diff --git a/docs/templates/config.rst.j2 b/docs/templates/config.rst.j2
index 0ff9e0f..cf0cb91 100644
--- a/docs/templates/config.rst.j2
+++ b/docs/templates/config.rst.j2
@@ -1,3 +1,5 @@
+.. _ansible_configuration_settings:
+
{% set name = 'Ansible Configuration Settings' -%}
{% set name_slug = 'config' -%}
diff --git a/docs/templates/list_of_CATEGORY_modules.rst.j2 b/docs/templates/list_of_CATEGORY_modules.rst.j2
index 301449e..808451b 100644
--- a/docs/templates/list_of_CATEGORY_modules.rst.j2
+++ b/docs/templates/list_of_CATEGORY_modules.rst.j2
@@ -7,11 +7,11 @@
@{ blurb }@
{% endif %}
-.. toctree:: :maxdepth: 1
+
{% if category['_modules'] %}
{% for module in category['_modules'] | sort %}
- @{ module }@{% if module_info[module]['deprecated'] %} **(D)**{% endif%} -- @{ module_info[module]['doc']['short_description'] }@ <@{ module }@_module>
+ * :ref:`@{ module }@_@{ plugin_type }@`{% if module_info[module]['deprecated'] %} **(D)**{% endif%}
{% endfor %}
{% endif %}
@@ -22,11 +22,10 @@
@{ name.title() }@
@{ '-' * name | length }@
-.. toctree:: :maxdepth: 1
+
{% for module in info['_modules'] | sort %}
-{# :ref:`@{ module }@`{% if module_info[module]['deprecated'] %} **(D)**{% endif%} @{ module_info[module]['doc']['short_description'] }@ #}
- @{ module }@{% if module_info[module]['deprecated'] %} **(D)**{% endif%} -- @{ module_info[module]['doc']['short_description'] }@ <@{ module }@_module>
+ * :ref:`@{ module }@_@{ plugin_type }@`{% if module_info[module]['deprecated'] %} **(D)**{% endif%}
{% endfor %}
{% endfor %}
diff --git a/docs/templates/list_of_CATEGORY_plugins.rst.j2 b/docs/templates/list_of_CATEGORY_plugins.rst.j2
index fba836c..0f9b611 100644
--- a/docs/templates/list_of_CATEGORY_plugins.rst.j2
+++ b/docs/templates/list_of_CATEGORY_plugins.rst.j2
@@ -25,7 +25,7 @@
.. toctree:: :maxdepth: 1
{% for module in info['_modules'] | sort %}
- :ref:`@{ module }@`{% if module_info[module]['deprecated'] %} **(D)**{% endif%} -- @{ module_info[module]['doc']['short_description'] }@
+ :ref:`@{ module }@_@{ plugin_type }@`{% if module_info[module]['deprecated'] %} **(D)**{% endif%} -- @{ module_info[module]['doc']['short_description'] }@
{% endfor %}
{% endfor %}
diff --git a/docs/templates/modules_by_support.rst.j2 b/docs/templates/modules_by_support.rst.j2
index d40c3e1..a8c8fa7 100644
--- a/docs/templates/modules_by_support.rst.j2
+++ b/docs/templates/modules_by_support.rst.j2
@@ -3,10 +3,8 @@
Modules Maintained by the @{ maintainers }@
``````````````````````````@{ '`' * maintainers | length }@
-.. toctree:: :maxdepth: 1
-
{% for module in modules | sort %}
- @{ module }@{% if module_info[module]['deprecated'] %} **(D)**{% endif %} - @{ module_info[module]['doc']['short_description'] }@ <@{ module }@_module>
+ * :ref:`@{ module }@_@{plugin_type}@`{% if module_info[module]['deprecated'] %} **(D)**{% endif%}
{% endfor %}
.. note::
diff --git a/docs/templates/plugin.rst.j2 b/docs/templates/plugin.rst.j2
index 84bc5a4..1936e89 100644
--- a/docs/templates/plugin.rst.j2
+++ b/docs/templates/plugin.rst.j2
@@ -1,6 +1,9 @@
:source: @{ source }@
-.. _@{ module }@:
+.. _@{ module }@_@{ plugin_type }@:
+{% for alias in aliases %}
+.. _@{ alias }@:
+{% endfor %}
{% if short_description %}
{% set title = module + ' - ' + short_description|convert_symbols_to_format %}
@@ -95,7 +98,7 @@ Parameters
<td>
<div class="outer-elbow-container">
{% for i in range(1, loop.depth) %}
- <div class="elbow-placeholder"></div>
+ <div class="elbow-placeholder">&nbsp;</div>
{% endfor %}
<div class="elbow-key">
<b>@{ key }@</b>
@@ -107,7 +110,7 @@ Parameters
{# default / choices #}
<td>
<div class="cell-border">
- {# Recalculate choices and boolean values #}
+ {# Turn boolean values in 'yes' and 'no' values #}
{% if value.default is defined %}
{% if value.default == true %}
{% set _x = value.update({'default': 'yes'}) %}
@@ -122,10 +125,16 @@ Parameters
{% if value.choices %}
<ul><b>Choices:</b>
{% for choice in value.choices %}
- {% if (value.default is string and choice == value.default) or (value.default is iterable and choice in value.default) %}
- <li type="disc"><div style="color: blue"><b>@{ choice | escape }@</b>&nbsp;&larr;</div></li>
+ {# Turn boolean values in 'yes' and 'no' values #}
+ {% if choice == true %}
+ {% set choice = 'yes' %}
+ {% elif choice == false %}
+ {% set choice = 'no' %}
+ {% endif %}
+ {% if (value.default is string and value.default == choice) or (value.default is iterable and value.default is not string and choice in value.default) %}
+ <li><div style="color: blue"><b>@{ choice | escape }@</b>&nbsp;&larr;</div></li>
{% else %}
- <li type="circle">@{ choice | escape }@</li>
+ <li>@{ choice | escape }@</li>
{% endif %}
{% endfor %}
</ul>
@@ -240,7 +249,7 @@ Facts returned by this module are added/updated in the ``hostvars`` host facts a
<td>
<div class="outer-elbow-container">
{% for i in range(1, loop.depth) %}
- <div class="elbow-placeholder"></div>
+ <div class="elbow-placeholder">&nbsp;</div>
{% endfor %}
<div class="elbow-key">
<b>@{ key }@</b>
@@ -304,8 +313,7 @@ Common return values are documented :ref:`here <common_return_values>`, the foll
<td>
<div class="outer-elbow-container">
{% for i in range(1, loop.depth) %}
- <div class="elbow-placeholder">
- </div>
+ <div class="elbow-placeholder">&nbsp;</div>
{% endfor %}
<div class="elbow-key">
<b>@{ key }@</b>
@@ -324,9 +332,9 @@ Common return values are documented :ref:`here <common_return_values>`, the foll
{% endfor %}
{% endif %}
<br/>
- {% if value.sample is defined and value.sample %}
+ {% if value.sample is defined and value.sample %}
<div style="font-size: smaller"><b>Sample:</b></div>
- {# TODO: The sample should be escaped, using | escape or | htmlify, but both mess things up beyond repair with dicts #}
+ {# TODO: The sample should be escaped, using |escape or |htmlify, but both mess things up beyond repair with dicts #}
<div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">@{ value.sample | replace('\n', '\n ') | html_ify }@</div>
{% endif %}
</div>
@@ -378,7 +386,7 @@ please refer to this `Knowledge Base article <https://access.redhat.com/articles
{% else %}
-This module is flagged as **deprecated** and will be removed in version { deprecated['removed_in'] | default('') | string | convert_symbols_to_format }@. For more information see :ref:`DEPRECATED`.
+This module is flagged as **deprecated** and will be removed in version @{ deprecated['removed_in'] | default('') | string | convert_symbols_to_format }@. For more information see `DEPRECATED`_.
{% endif %}
@@ -394,4 +402,8 @@ Author
{% endif %}
.. hint::
- If you notice any issues in this documentation you can `edit this document <https://github.com/ansible/ansible/edit/devel/lib/ansible/modules/@{ source }@?description=%3C!---%20Your%20description%20here%20--%3E%0A%0A+label:%20docsite_pr>`_ to improve it.
+{% if plugin_type == 'module' %}
+ If you notice any issues in this documentation you can `edit this document <https://github.com/ansible/ansible/edit/devel/lib/ansible/modules/@{ source }@?description=%3C!---%20Your%20description%20here%20--%3E%0A%0A%2Blabel:%20docsite_pr>`_ to improve it.
+{% else %}
+ If you notice any issues in this documentation you can `edit this document <https://github.com/ansible/ansible/edit/devel/lib/ansible/plugins/@{ plugin_type }@/@{ source }@_` to improve it.
+{% endif %}
diff --git a/lib/ansible/cli/doc.py b/lib/ansible/cli/doc.py
index dc001ee..a0f17d6 100644
--- a/lib/ansible/cli/doc.py
+++ b/lib/ansible/cli/doc.py
@@ -72,8 +72,7 @@ class DocCLI(CLI):
help='**For internal testing only** Show documentation for all plugins.')
self.parser.add_option("-t", "--type", action="store", default='module', dest='type', type='choice',
help='Choose which plugin type (defaults to "module")',
- choices=['cache', 'callback', 'connection', 'inventory', 'lookup', 'module', 'shell', 'strategy', 'vars'])
-
+ choices=C.DOCUMENTABLE_PLUGINS)
super(DocCLI, self).parse()
if [self.options.all_plugins, self.options.list_dir, self.options.list_files, self.options.show_snippet].count(True) > 1:
diff --git a/lib/ansible/cli/inventory.py b/lib/ansible/cli/inventory.py
index cb0e825..008fc74 100644
--- a/lib/ansible/cli/inventory.py
+++ b/lib/ansible/cli/inventory.py
@@ -78,6 +78,10 @@ class InventoryCLI(CLI):
basedir_opts=True,
)
+ # remove unused default options
+ self.parser.remove_option('--limit')
+ self.parser.remove_option('--list-hosts')
+
# Actions
action_group = optparse.OptionGroup(self.parser, "Actions", "One of following must be used on invocation, ONLY ONE!")
action_group.add_option("--list", action="store_true", default=False, dest='list', help='Output all hosts info, works as inventory script')
@@ -86,8 +90,6 @@ class InventoryCLI(CLI):
help='create inventory graph, if supplying pattern it must be a valid group name')
self.parser.add_option_group(action_group)
- # Options
-
# graph
self.parser.add_option("-y", "--yaml", action="store_true", default=False, dest='yaml',
help='Use YAML format instead of default JSON, ignored for --graph')
@@ -228,8 +230,7 @@ class InventoryCLI(CLI):
# get info from inventory source
res = group.get_vars()
- # FIXME: add switch to skip vars plugins
- # add vars plugin info
+ # FIXME: add switch to skip vars plugins, add vars plugin info
for inventory_dir in self.inventory._sources:
res = combine_vars(res, self.get_plugin_vars(inventory_dir, group))
@@ -299,7 +300,7 @@ class InventoryCLI(CLI):
result.append(self._graph_name(host.name, depth))
result.extend(self._show_vars(host.get_vars(), depth + 1))
- result.extend(self._show_vars(group.get_vars(), depth))
+ result.extend(self._show_vars(self._get_group_variables(group), depth))
return result
diff --git a/lib/ansible/cli/pull.py b/lib/ansible/cli/pull.py
index 1709da4..832be72 100644
--- a/lib/ansible/cli/pull.py
+++ b/lib/ansible/cli/pull.py
@@ -30,7 +30,7 @@ import time
from ansible.cli import CLI
from ansible.errors import AnsibleOptionsError
-from ansible.module_utils._text import to_native
+from ansible.module_utils._text import to_native, to_text
from ansible.plugins.loader import module_loader
from ansible.utils.cmd_functions import run_cmd
@@ -238,14 +238,14 @@ class PullCLI(CLI):
# RUN the Checkout command
display.debug("running ansible with VCS module to checkout repo")
display.vvvv('EXEC: %s' % cmd)
- rc, out, err = run_cmd(cmd, live=True)
+ rc, b_out, b_err = run_cmd(cmd, live=True)
if rc != 0:
if self.options.force:
display.warning("Unable to update repository. Continuing with (forced) run of playbook.")
else:
return rc
- elif self.options.ifchanged and '"changed": true' not in out:
+ elif self.options.ifchanged and b'"changed": true' not in b_out:
display.display("Repository has not changed, quitting.")
return 0
@@ -287,14 +287,14 @@ class PullCLI(CLI):
# RUN THE PLAYBOOK COMMAND
display.debug("running ansible-playbook to do actual work")
display.debug('EXEC: %s' % cmd)
- rc, out, err = run_cmd(cmd, live=True)
+ rc, b_out, b_err = run_cmd(cmd, live=True)
if self.options.purge:
os.chdir('/')
try:
shutil.rmtree(self.options.dest)
except Exception as e:
- display.error("Failed to remove %s: %s" % (self.options.dest, str(e)))
+ display.error(u"Failed to remove %s: %s" % (self.options.dest, to_text(e)))
return rc
diff --git a/lib/ansible/cli/vault.py b/lib/ansible/cli/vault.py
index 4b71883..cbc56b8 100644
--- a/lib/ansible/cli/vault.py
+++ b/lib/ansible/cli/vault.py
@@ -107,7 +107,7 @@ class VaultCLI(CLI):
self.parser.set_usage("usage: %prog rekey [options] file_name")
# For encrypting actions, we can also specify which of multiple vault ids should be used for encrypting
- if self.action in ['create', 'encrypt', 'encrypt_string', 'rekey']:
+ if self.action in ['create', 'encrypt', 'encrypt_string', 'rekey', 'edit']:
self.parser.add_option('--encrypt-vault-id', default=[], dest='encrypt_vault_id',
action='store', type='string',
help='the vault id used to encrypt (required if more than vault-id is provided)')
@@ -181,7 +181,7 @@ class VaultCLI(CLI):
if not vault_secrets:
raise AnsibleOptionsError("A vault password is required to use Ansible's Vault")
- if self.action in ['encrypt', 'encrypt_string', 'create', 'edit']:
+ if self.action in ['encrypt', 'encrypt_string', 'create']:
encrypt_vault_id = None
# no --encrypt-vault-id self.options.encrypt_vault_id for 'edit'
diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py
index 16d57db..cdf2d5f 100644
--- a/lib/ansible/constants.py
+++ b/lib/ansible/constants.py
@@ -91,6 +91,10 @@ DEFAULT_SUDO_PASS = None
DEFAULT_REMOTE_PASS = None
DEFAULT_SUBSET = None
DEFAULT_SU_PASS = None
+# FIXME: expand to other plugins, but never doc fragments
+CONFIGURABLE_PLUGINS = ('cache', 'callback', 'connection', 'inventory', 'lookup', 'shell')
+# NOTE: always update the docs/docsite/Makefile to match
+DOCUMENTABLE_PLUGINS = CONFIGURABLE_PLUGINS + ('module', 'strategy', 'vars')
IGNORE_FILES = ("COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION", "GUIDELINES") # ignore during module search
INTERNAL_RESULT_KEYS = ('add_host', 'add_group')
LOCALHOST = ('127.0.0.1', 'localhost', '::1')
diff --git a/lib/ansible/executor/module_common.py b/lib/ansible/executor/module_common.py
index 893feab..009eb06 100644
--- a/lib/ansible/executor/module_common.py
+++ b/lib/ansible/executor/module_common.py
@@ -826,7 +826,7 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
become_required = True
for m in set(module_names):
- m = to_text(m)
+ m = to_text(m).rstrip() # tolerate windows line endings
mu_path = ps_module_utils_loader.find_plugin(m, ".psm1")
if not mu_path:
raise AnsibleError('Could not find imported module support code for \'%s\'.' % m)
@@ -874,7 +874,7 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
return (b_module_data, module_style, shebang)
-def modify_module(module_name, module_path, module_args, task_vars=None, templar=None, module_compression='ZIP_STORED', async_timeout=0, become=False,
+def modify_module(module_name, module_path, module_args, templar, task_vars=None, module_compression='ZIP_STORED', async_timeout=0, become=False,
become_method=None, become_user=None, become_password=None, become_flags=None, environment=None):
"""
Used to insert chunks of code into modules before transfer rather than
diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py
index e3a1201..35a8766 100644
--- a/lib/ansible/executor/task_executor.py
+++ b/lib/ansible/executor/task_executor.py
@@ -233,7 +233,11 @@ class TaskExecutor:
elif self._task.loop:
items = templar.template(self._task.loop)
if not isinstance(items, list):
- raise AnsibleError("Invalid data passed to 'loop' it requires a list, got this instead: %s" % items)
+ raise AnsibleError(
+ "Invalid data passed to 'loop', it requires a list, got this instead: %s."
+ " Hint: If you passed a list/dict of just one element,"
+ " try adding wantlist=True to your lookup invocation or use q/query instead of lookup." % items
+ )
# now we restore any old job variables that may have been modified,
# and delete them if they were in the play context vars but not in
@@ -593,7 +597,7 @@ class TaskExecutor:
failed_when_result = False
return failed_when_result
- if 'ansible_facts' in result:
+ if 'ansible_facts' in result and self._task.action not in ('set_fact', 'include_vars'):
vars_copy.update(namespace_facts(result['ansible_facts']))
if C.INJECT_FACTS_AS_VARS:
vars_copy.update(clean_facts(result['ansible_facts']))
@@ -651,7 +655,7 @@ class TaskExecutor:
if self._task.register:
variables[self._task.register] = wrap_var(result)
- if 'ansible_facts' in result:
+ if 'ansible_facts' in result and self._task.action not in ('set_fact', 'include_vars'):
variables.update(namespace_facts(result['ansible_facts']))
if C.INJECT_FACTS_AS_VARS:
variables.update(clean_facts(result['ansible_facts']))
@@ -858,9 +862,21 @@ class TaskExecutor:
master, slave = pty.openpty()
python = sys.executable
- # Assume ansible-connection is in the same dir as sys.argv[0]
- ansible_connection = os.path.join(os.path.dirname(sys.argv[0]), 'ansible-connection')
- p = subprocess.Popen([python, ansible_connection, to_text(os.getppid())], stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ def find_file_in_path(filename):
+ # Check $PATH first, followed by same directory as sys.argv[0]
+ paths = os.environ['PATH'].split(os.pathsep) + [os.path.dirname(sys.argv[0])]
+ for dirname in paths:
+ fullpath = os.path.join(dirname, filename)
+ if os.path.isfile(fullpath):
+ return fullpath
+
+ raise AnsibleError("Unable to find location of '%s'" % filename)
+
+ p = subprocess.Popen(
+ [python, find_file_in_path('ansible-connection'), to_text(os.getppid())],
+ stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
stdin = os.fdopen(master, 'wb', 0)
os.close(slave)
@@ -883,7 +899,8 @@ class TaskExecutor:
else:
try:
result = json.loads(to_text(stderr, errors='surrogate_then_replace'))
- except json.decoder.JSONDecodeError:
+ except getattr(json.decoder, 'JSONDecodeError', ValueError):
+ # JSONDecodeError only available on Python 3.5+
result = {'error': to_text(stderr, errors='surrogate_then_replace')}
if 'messages' in result:
diff --git a/lib/ansible/inventory/data.py b/lib/ansible/inventory/data.py
index 04308a0..dc730ff 100644
--- a/lib/ansible/inventory/data.py
+++ b/lib/ansible/inventory/data.py
@@ -128,7 +128,7 @@ class InventoryData(object):
if self.groups['ungrouped'] in mygroups:
# clear ungrouped of any incorrectly stored by parser
if set(mygroups).difference(set([self.groups['all'], self.groups['ungrouped']])):
- host.remove_group(self.groups['ungrouped'])
+ self.groups['ungrouped'].remove_host(host)
elif not host.implicit:
# add ungrouped hosts to ungrouped, except implicit
diff --git a/lib/ansible/inventory/group.py b/lib/ansible/inventory/group.py
index 4847e6f..859781c 100644
--- a/lib/ansible/inventory/group.py
+++ b/lib/ansible/inventory/group.py
@@ -19,6 +19,8 @@ __metaclass__ = type
from ansible.errors import AnsibleError
+from itertools import chain
+
class Group:
''' a group of ansible hosts '''
@@ -80,6 +82,38 @@ class Group:
g.deserialize(parent_data)
self.parent_groups.append(g)
+ def _walk_relationship(self, rel):
+ '''
+ Given `rel` that is an iterable property of Group,
+ consitituting a directed acyclic graph among all groups,
+ Returns a set of all groups in full tree
+ A B C
+ | / | /
+ | / | /
+ D -> E
+ | / vertical connections
+ | / are directed upward
+ F
+ Called on F, returns set of (A, B, C, D, E)
+ '''
+ seen = set([])
+ unprocessed = set(getattr(self, rel))
+
+ while unprocessed:
+ seen.update(unprocessed)
+ unprocessed = set(chain.from_iterable(
+ getattr(g, rel) for g in unprocessed
+ ))
+ unprocessed.difference_update(seen)
+
+ return seen
+
+ def get_ancestors(self):
+ return self._walk_relationship('parent_groups')
+
+ def get_descendants(self):
+ return self._walk_relationship('child_groups')
+
@property
def host_names(self):
if self._hosts is None:
@@ -96,6 +130,17 @@ class Group:
# don't add if it's already there
if group not in self.child_groups:
+
+ # prepare list of group's new ancestors this edge creates
+ start_ancestors = group.get_ancestors()
+ new_ancestors = self.get_ancestors()
+ if group in new_ancestors:
+ raise AnsibleError(
+ "Adding group '%s' as child to '%s' creates a recursive "
+ "dependency loop." % (group.name, self.name))
+ new_ancestors.add(self)
+ new_ancestors.difference_update(start_ancestors)
+
self.child_groups.append(group)
# update the depth of the child
@@ -109,18 +154,28 @@ class Group:
if self.name not in [g.name for g in group.parent_groups]:
group.parent_groups.append(self)
for h in group.get_hosts():
- h.populate_ancestors()
+ h.populate_ancestors(additions=new_ancestors)
self.clear_hosts_cache()
def _check_children_depth(self):
- try:
- for group in self.child_groups:
- group.depth = max([self.depth + 1, group.depth])
- group._check_children_depth()
- except RuntimeError:
- raise AnsibleError("The group named '%s' has a recursive dependency loop." % self.name)
+ depth = self.depth
+ start_depth = self.depth # self.depth could change over loop
+ seen = set([])
+ unprocessed = set(self.child_groups)
+
+ while unprocessed:
+ seen.update(unprocessed)
+ depth += 1
+ to_process = unprocessed.copy()
+ unprocessed = set([])
+ for g in to_process:
+ if g.depth < depth:
+ g.depth = depth
+ unprocessed.update(g.child_groups)
+ if depth - start_depth > len(seen):
+ raise AnsibleError("The group named '%s' has a recursive dependency loop." % self.name)
def add_host(self, host):
if host.name not in self.host_names:
@@ -147,8 +202,8 @@ class Group:
def clear_hosts_cache(self):
self._hosts_cache = None
- for g in self.parent_groups:
- g.clear_hosts_cache()
+ for g in self.get_ancestors():
+ g._hosts_cache = None
def get_hosts(self):
@@ -160,8 +215,8 @@ class Group:
hosts = []
seen = {}
- for kid in self.child_groups:
- kid_hosts = kid.get_hosts()
+ for kid in self.get_descendants():
+ kid_hosts = kid.hosts
for kk in kid_hosts:
if kk not in seen:
seen[kk] = 1
@@ -179,18 +234,6 @@ class Group:
def get_vars(self):
return self.vars.copy()
- def _get_ancestors(self):
-
- results = {}
- for g in self.parent_groups:
- results[g.name] = g
- results.update(g._get_ancestors())
- return results
-
- def get_ancestors(self):
-
- return self._get_ancestors().values()
-
def set_priority(self, priority):
try:
self.priority = int(priority)
diff --git a/lib/ansible/inventory/host.py b/lib/ansible/inventory/host.py
index 647e00d..4327273 100644
--- a/lib/ansible/inventory/host.py
+++ b/lib/ansible/inventory/host.py
@@ -101,17 +101,22 @@ class Host:
def get_name(self):
return self.name
- def populate_ancestors(self):
+ def populate_ancestors(self, additions=None):
# populate ancestors
- for group in self.groups:
- self.add_group(group)
+ if additions is None:
+ for group in self.groups:
+ self.add_group(group)
+ else:
+ for group in additions:
+ if group not in self.groups:
+ self.groups.append(group)
def add_group(self, group):
# populate ancestors first
for oldg in group.get_ancestors():
if oldg not in self.groups:
- self.add_group(oldg)
+ self.groups.append(oldg)
# actually add group
if group not in self.groups:
diff --git a/lib/ansible/inventory/manager.py b/lib/ansible/inventory/manager.py
index 133809e..719a333 100644
--- a/lib/ansible/inventory/manager.py
+++ b/lib/ansible/inventory/manager.py
@@ -270,7 +270,7 @@ class InventoryManager(object):
failures.append({'src': source, 'plugin': plugin_name, 'exc': e})
except Exception as e:
display.debug('%s failed to parse %s' % (plugin_name, to_text(source)))
- failures.append({'src': source, 'plugin': plugin_name, 'exc': e})
+ failures.append({'src': source, 'plugin': plugin_name, 'exc': AnsibleError(e)})
else:
display.debug('%s did not meet %s requirements' % (to_text(source), plugin_name))
else:
diff --git a/lib/ansible/module_utils/connection.py b/lib/ansible/module_utils/connection.py
index 28cc464..7e1aceb 100644
--- a/lib/ansible/module_utils/connection.py
+++ b/lib/ansible/module_utils/connection.py
@@ -111,8 +111,10 @@ class Connection(object):
req = request_builder(name, *args, **kwargs)
reqid = req['id']
+ troubleshoot = 'http://docs.ansible.com/ansible/latest/network/user_guide/network_debug_troubleshooting.html#category-socket-path-issue'
+
if not os.path.exists(self.socket_path):
- raise ConnectionError('socket_path does not exist or cannot be found')
+ raise ConnectionError('socket_path does not exist or cannot be found. Please check %s' % troubleshoot)
try:
data = json.dumps(req)
@@ -120,7 +122,8 @@ class Connection(object):
response = json.loads(out)
except socket.error as e:
- raise ConnectionError('unable to connect to socket', err=to_text(e, errors='surrogate_then_replace'), exception=traceback.format_exc())
+ raise ConnectionError('unable to connect to socket. Please check %s' % troubleshoot, err=to_text(e, errors='surrogate_then_replace'),
+ exception=traceback.format_exc())
if response['id'] != reqid:
raise ConnectionError('invalid json-rpc id received')
diff --git a/lib/ansible/module_utils/facts/hardware/freebsd.py b/lib/ansible/module_utils/facts/hardware/freebsd.py
index 0e7002b..8d0bc61 100644
--- a/lib/ansible/module_utils/facts/hardware/freebsd.py
+++ b/lib/ansible/module_utils/facts/hardware/freebsd.py
@@ -66,12 +66,18 @@ class FreeBSDHardware(Hardware):
def get_cpu_facts(self):
cpu_facts = {}
cpu_facts['processor'] = []
- rc, out, err = self.module.run_command("/sbin/sysctl -n hw.ncpu")
- cpu_facts['processor_count'] = out.strip()
+ sysctl = self.module.get_bin_path('sysctl')
+ if sysctl:
+ rc, out, err = self.module.run_command("%s -n hw.ncpu" % sysctl, check_rc=False)
+ cpu_facts['processor_count'] = out.strip()
dmesg_boot = get_file_content(FreeBSDHardware.DMESG_BOOT)
if not dmesg_boot:
- rc, dmesg_boot, err = self.module.run_command("/sbin/dmesg")
+ try:
+ rc, dmesg_boot, err = self.module.run_command(self.module.get_bin_path("dmesg"), check_rc=False)
+ except Exception:
+ dmesg_boot = ''
+
for line in dmesg_boot.splitlines():
if 'CPU:' in line:
cpu = re.sub(r'CPU:\s+', r"", line)
@@ -84,29 +90,34 @@ class FreeBSDHardware(Hardware):
def get_memory_facts(self):
memory_facts = {}
- rc, out, err = self.module.run_command("/sbin/sysctl vm.stats")
- for line in out.splitlines():
- data = line.split()
- if 'vm.stats.vm.v_page_size' in line:
- pagesize = int(data[1])
- if 'vm.stats.vm.v_page_count' in line:
- pagecount = int(data[1])
- if 'vm.stats.vm.v_free_count' in line:
- freecount = int(data[1])
- memory_facts['memtotal_mb'] = pagesize * pagecount // 1024 // 1024
- memory_facts['memfree_mb'] = pagesize * freecount // 1024 // 1024
- # Get swapinfo. swapinfo output looks like:
- # Device 1M-blocks Used Avail Capacity
- # /dev/ada0p3 314368 0 314368 0%
- #
- rc, out, err = self.module.run_command("/usr/sbin/swapinfo -k")
- lines = out.splitlines()
- if len(lines[-1]) == 0:
- lines.pop()
- data = lines[-1].split()
- if data[0] != 'Device':
- memory_facts['swaptotal_mb'] = int(data[1]) // 1024
- memory_facts['swapfree_mb'] = int(data[3]) // 1024
+ sysctl = self.module.get_bin_path('sysctl')
+ if sysctl:
+ rc, out, err = self.module.run_command("%s vm.stats" % sysctl, check_rc=False)
+ for line in out.splitlines():
+ data = line.split()
+ if 'vm.stats.vm.v_page_size' in line:
+ pagesize = int(data[1])
+ if 'vm.stats.vm.v_page_count' in line:
+ pagecount = int(data[1])
+ if 'vm.stats.vm.v_free_count' in line:
+ freecount = int(data[1])
+ memory_facts['memtotal_mb'] = pagesize * pagecount // 1024 // 1024
+ memory_facts['memfree_mb'] = pagesize * freecount // 1024 // 1024
+
+ swapinfo = self.module.get_bin_path('swapinfo')
+ if swapinfo:
+ # Get swapinfo. swapinfo output looks like:
+ # Device 1M-blocks Used Avail Capacity
+ # /dev/ada0p3 314368 0 314368 0%
+ #
+ rc, out, err = self.module.run_command("%s -k" % swapinfo)
+ lines = out.splitlines()
+ if len(lines[-1]) == 0:
+ lines.pop()
+ data = lines[-1].split()
+ if data[0] != 'Device':
+ memory_facts['swaptotal_mb'] = int(data[1]) // 1024
+ memory_facts['swapfree_mb'] = int(data[3]) // 1024
return memory_facts
diff --git a/lib/ansible/module_utils/facts/system/chroot.py b/lib/ansible/module_utils/facts/system/chroot.py
index 39ccf96..5eb89d6 100644
--- a/lib/ansible/module_utils/facts/system/chroot.py
+++ b/lib/ansible/module_utils/facts/system/chroot.py
@@ -20,7 +20,7 @@ def is_chroot():
# check if my file system is the root one
proc_root = os.stat('/proc/1/root/.')
is_chroot = my_root.st_ino != proc_root.st_ino or my_root.st_dev != proc_root.st_dev
- except:
+ except Exception:
# I'm not root or no proc, fallback to checking it is inode #2
is_chroot = (my_root.st_ino != 2)
diff --git a/lib/ansible/module_utils/facts/utils.py b/lib/ansible/module_utils/facts/utils.py
index 728934c..5d78eab 100644
--- a/lib/ansible/module_utils/facts/utils.py
+++ b/lib/ansible/module_utils/facts/utils.py
@@ -29,7 +29,7 @@ def get_file_content(path, default=None, strip=True):
data = default
finally:
datafile.close()
- except:
+ except Exception:
# ignore errors as some jails/containers might have readable permissions but not allow reads to proc
# done in 2 blocks for 2.4 compat
pass
diff --git a/lib/ansible/module_utils/facts/virtual/linux.py b/lib/ansible/module_utils/facts/virtual/linux.py
index a0ad545..fece8d8 100644
--- a/lib/ansible/module_utils/facts/virtual/linux.py
+++ b/lib/ansible/module_utils/facts/virtual/linux.py
@@ -236,13 +236,15 @@ class LinuxVirtual(Virtual):
# In older Linux Kernel versions, /sys filesystem is not available
# dmidecode is the safest option to parse virtualization related values
dmi_bin = self.module.get_bin_path('dmidecode')
- (rc, out, err) = self.module.run_command('%s -s system-product-name' % dmi_bin)
- if rc == 0:
- # Strip out commented lines (specific dmidecode output)
- vendor_name = ''.join([line.strip() for line in out.splitlines() if not line.startswith('#')])
- if vendor_name in ['VMware Virtual Platform', 'VMware7,1']:
- virtual_facts['virtualization_type'] = 'VMware'
- virtual_facts['virtualization_role'] = 'guest'
+ # We still want to continue even if dmidecode is not available
+ if dmi_bin is not None:
+ (rc, out, err) = self.module.run_command('%s -s system-product-name' % dmi_bin)
+ if rc == 0:
+ # Strip out commented lines (specific dmidecode output)
+ vendor_name = ''.join([line.strip() for line in out.splitlines() if not line.startswith('#')])
+ if vendor_name.startwith('VMware'):
+ virtual_facts['virtualization_type'] = 'VMware'
+ virtual_facts['virtualization_role'] = 'guest'
# If none of the above matches, return 'NA' for virtualization_type
# and virtualization_role. This allows for proper grouping.
diff --git a/lib/ansible/module_utils/k8s/lookup.py b/lib/ansible/module_utils/k8s/lookup.py
index 6d175ea..a05f3ca 100644
--- a/lib/ansible/module_utils/k8s/lookup.py
+++ b/lib/ansible/module_utils/k8s/lookup.py
@@ -88,8 +88,12 @@ class KubernetesLookup(object):
self.kind = to_snake(self.kind)
self.helper = self.get_helper(self.api_version, self.kind)
+ auth_args = ('host', 'api_key', 'kubeconfig', 'context', 'username', 'password',
+ 'cert_file', 'key_file', 'ssl_ca_cert', 'verify_ssl')
+
for arg in AUTH_ARG_SPEC:
- self.connection[arg] = kwargs.get(arg)
+ if arg in auth_args and kwargs.get(arg) is not None:
+ self.connection[arg] = kwargs.get(arg)
try:
self.helper.set_client_config(**self.connection)
diff --git a/lib/ansible/module_utils/network/eos/eos.py b/lib/ansible/module_utils/network/eos/eos.py
index 343104e..d254ea7 100644
--- a/lib/ansible/module_utils/network/eos/eos.py
+++ b/lib/ansible/module_utils/network/eos/eos.py
@@ -32,7 +32,7 @@ import time
from ansible.module_utils._text import to_text, to_native
from ansible.module_utils.basic import env_fallback, return_values
-from ansible.module_utils.connection import Connection
+from ansible.module_utils.connection import Connection, ConnectionError
from ansible.module_utils.network.common.utils import to_list, ComplexList
from ansible.module_utils.six import iteritems
from ansible.module_utils.urls import fetch_url
@@ -121,6 +121,13 @@ class Cli:
return self._connection
+ def close_session(self, session):
+ conn = self._get_connection()
+ # to close session gracefully execute abort in top level session prompt.
+ conn.get('end')
+ conn.get('configure session %s' % session)
+ conn.get('abort')
+
@property
def supports_sessions(self):
if self._session_support is not None:
@@ -206,8 +213,10 @@ class Cli:
try:
self.send_config(commands)
- except:
- conn.get('abort')
+ except ConnectionError as exc:
+ conn.get('end')
+ message = getattr(exc, 'err', exc)
+ self._module.fail_json(msg="Error on executing commands %s" % commands, data=to_text(message, errors='surrogate_then_replace'))
conn.get('end')
return {}
@@ -222,7 +231,12 @@ class Cli:
pass
if not all((bool(use_session), self.supports_sessions)):
- return self.configure(self, commands)
+ if commit:
+ return self.configure(self, commands)
+ else:
+ self._module.warn("EOS can not check config without config session")
+ result = {'changed': True}
+ return result
conn = self._get_connection()
session = 'ansible_%s' % int(time.time())
@@ -235,8 +249,10 @@ class Cli:
try:
self.send_config(commands)
- except:
- conn.get('abort')
+ except ConnectionError as exc:
+ self.close_session(session)
+ message = getattr(exc, 'err', exc)
+ self._module.fail_json(msg="Error on executing commands %s" % commands, data=to_text(message, errors='surrogate_then_replace'))
out = conn.get('show session-config diffs')
if out:
@@ -245,7 +261,7 @@ class Cli:
if commit:
conn.get('commit')
else:
- conn.get('abort')
+ self.close_session(session)
return result
@@ -396,8 +412,19 @@ class Eapi:
fallback to using configure() to load the commands. If that happens,
there will be no returned diff or session values
"""
- if not self.supports_sessions:
- return self.configure(self, config)
+ use_session = os.getenv('ANSIBLE_EOS_USE_SESSIONS', True)
+ try:
+ use_session = int(use_session)
+ except ValueError:
+ pass
+
+ if not all((bool(use_session), self.supports_sessions)):
+ if commit:
+ return self.configure(self, config)
+ else:
+ self._module.warn("EOS can not check config without config session")
+ result = {'changed': True}
+ return result
session = 'ansible_%s' % int(time.time())
result = {'session': session}
diff --git a/lib/ansible/module_utils/network/junos/junos.py b/lib/ansible/module_utils/network/junos/junos.py
index 2a1d085..c626c46 100644
--- a/lib/ansible/module_utils/network/junos/junos.py
+++ b/lib/ansible/module_utils/network/junos/junos.py
@@ -180,9 +180,9 @@ def locked_config(module):
unlock_configuration(module)
-def discard_changes(module, exit=False):
+def discard_changes(module):
conn = get_connection(module)
- return conn.discard_changes(exit=exit)
+ return conn.discard_changes()
def get_diff(module, rollback='0'):
diff --git a/lib/ansible/module_utils/network/nso/nso.py b/lib/ansible/module_utils/network/nso/nso.py
index a9c89ca..e5bbedb 100644
--- a/lib/ansible/module_utils/network/nso/nso.py
+++ b/lib/ansible/module_utils/network/nso/nso.py
@@ -300,15 +300,16 @@ class ValueBuilder(object):
return 'Value<path={0}, state={1}, value={2}>'.format(
self.path, self.state, self.value)
- def __init__(self, client):
+ def __init__(self, client, mode='config'):
self._client = client
+ self._mode = mode
self._schema_cache = {}
self._module_prefix_map_cache = None
self._values = []
self._values_dirty = False
def build(self, parent, maybe_qname, value, schema=None):
- qname, name = self._get_prefix_name(maybe_qname)
+ qname, name = self.get_prefix_name(maybe_qname)
if name is None:
path = parent
else:
@@ -329,16 +330,16 @@ class ValueBuilder(object):
self._add_value(path, State.PRESENT, None, deps)
else:
if maybe_qname is None:
- value_type = self._get_type(path)
+ value_type = self.get_type(path)
else:
value_type = self._get_child_type(parent, qname)
if 'identityref' in value_type:
if isinstance(value, list):
value = [ll_v for ll_v, t_ll_v
- in [self._get_prefix_name(v) for v in value]]
+ in [self.get_prefix_name(v) for v in value]]
else:
- value, t_value = self._get_prefix_name(value)
+ value, t_value = self.get_prefix_name(value)
self._add_value(path, State.SET, value, deps)
elif isinstance(value, dict):
self._build_dict(path, schema, value)
@@ -420,7 +421,7 @@ class ValueBuilder(object):
def _build_dict(self, path, schema, value):
keys = schema.get('key', [])
for dict_key, dict_value in value.items():
- qname, name = self._get_prefix_name(dict_key)
+ qname, name = self.get_prefix_name(dict_key)
if dict_key in ('__state', ) or name in keys:
continue
@@ -429,16 +430,25 @@ class ValueBuilder(object):
def _build_leaf_list(self, path, schema, value):
deps = schema.get('deps', [])
- entry_type = self._get_type(path, schema)
- # remove leaf list if treated as a list and then re-create the
- # expected list entries.
- self._add_value(path, State.ABSENT, None, deps)
+ entry_type = self.get_type(path, schema)
+
+ if self._mode == 'verify':
+ for entry in value:
+ if 'identityref' in entry_type:
+ entry, t_entry = self.get_prefix_name(entry)
+ entry_path = '{0}{{{1}}}'.format(path, entry)
+ if not self._client.exists(entry_path):
+ self._add_value(entry_path, State.ABSENT, None, deps)
+ else:
+ # remove leaf list if treated as a list and then re-create the
+ # expected list entries.
+ self._add_value(path, State.ABSENT, None, deps)
- for entry in value:
- if 'identityref' in entry_type:
- entry, t_entry = self._get_prefix_name(entry)
- entry_path = '{0}{{{1}}}'.format(path, entry)
- self._add_value(entry_path, State.PRESENT, None, deps)
+ for entry in value:
+ if 'identityref' in entry_type:
+ entry, t_entry = self.get_prefix_name(entry)
+ entry_path = '{0}{{{1}}}'.format(path, entry)
+ self._add_value(entry_path, State.PRESENT, None, deps)
def _build_list(self, path, schema, value):
deps = schema.get('deps', [])
@@ -470,7 +480,7 @@ class ValueBuilder(object):
value_type = self._get_child_type(path, key)
if 'identityref' in value_type:
- value, t_value = self._get_prefix_name(value)
+ value, t_value = self.get_prefix_name(value)
key_parts.append(self._quote_key(value))
return ' '.join(key_parts)
@@ -513,7 +523,7 @@ class ValueBuilder(object):
self._values.append(ValueBuilder.Value(path, state, value, deps))
self._values_dirty = True
- def _get_prefix_name(self, qname):
+ def get_prefix_name(self, qname):
if not isinstance(qname, (str, unicode)):
return qname, None
if ':' not in qname:
@@ -536,9 +546,9 @@ class ValueBuilder(object):
parent_schema = all_schema['data']
meta = all_schema['meta']
schema = self._find_child(parent_path, parent_schema, key)
- return self._get_type(parent_path, schema, meta)
+ return self.get_type(parent_path, schema, meta)
- def _get_type(self, path, schema=None, meta=None):
+ def get_type(self, path, schema=None, meta=None):
if schema is None or meta is None:
all_schema = self._ensure_schema_cached(path)
schema = all_schema['data']
@@ -669,7 +679,8 @@ def verify_version_str(version_str, required_versions):
def normalize_value(expected_value, value, key):
if value is None:
return None
- if isinstance(expected_value, bool):
+ if (isinstance(expected_value, bool) and
+ isinstance(value, (str, unicode))):
return value == 'true'
if isinstance(expected_value, int):
try:
diff --git a/lib/ansible/module_utils/network/onyx/onyx.py b/lib/ansible/module_utils/network/onyx/onyx.py
index e976a65..d439d9f 100644
--- a/lib/ansible/module_utils/network/onyx/onyx.py
+++ b/lib/ansible/module_utils/network/onyx/onyx.py
@@ -140,18 +140,24 @@ def get_interfaces_config(module, interface_type, flags=None, json_fmt=True):
return show_cmd(module, cmd, json_fmt)
+def show_version(module):
+ return show_cmd(module, "show version")
+
+
def get_bgp_summary(module):
cmd = "show running-config protocol bgp"
return show_cmd(module, cmd, json_fmt=False, fail_on_error=False)
class BaseOnyxModule(object):
+ ONYX_API_VERSION = "3.6.6000"
def __init__(self):
self._module = None
self._commands = list()
self._current_config = None
self._required_config = None
+ self._os_version = None
def init_module(self):
pass
@@ -162,6 +168,11 @@ class BaseOnyxModule(object):
def get_required_config(self):
pass
+ def _get_os_version(self):
+ version_data = show_version(self._module)
+ return self.get_config_attr(
+ version_data, "Product release")
+
# pylint: disable=unused-argument
def check_declarative_intent_params(self, result):
return None
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1
index 9b85f49..ce94827 100644
--- a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1
@@ -2,6 +2,7 @@
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
$process_util = @"
+using Microsoft.Win32.SafeHandles;
using System;
using System.Collections;
using System.IO;
@@ -42,9 +43,9 @@ namespace Ansible
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
- public IntPtr hStdInput;
- public IntPtr hStdOutput;
- public IntPtr hStdError;
+ public SafeFileHandle hStdInput;
+ public SafeFileHandle hStdOutput;
+ public SafeFileHandle hStdError;
public STARTUPINFO()
{
cb = Marshal.SizeOf(this);
@@ -88,7 +89,7 @@ namespace Ansible
{
public NativeWaitHandle(IntPtr handle)
{
- this.Handle = handle;
+ this.SafeWaitHandle = new SafeWaitHandle(handle, false);
}
}
@@ -110,7 +111,6 @@ namespace Ansible
public class CommandUtil
{
private static UInt32 CREATE_UNICODE_ENVIRONMENT = 0x000000400;
- private static UInt32 CREATE_NEW_CONSOLE = 0x00000010;
private static UInt32 EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
@@ -130,43 +130,22 @@ namespace Ansible
[DllImport("kernel32.dll")]
public static extern bool CreatePipe(
- out IntPtr hReadPipe,
- out IntPtr hWritePipe,
+ out SafeFileHandle hReadPipe,
+ out SafeFileHandle hWritePipe,
SECURITY_ATTRIBUTES lpPipeAttributes,
uint nSize);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetHandleInformation(
- IntPtr hObject,
+ SafeFileHandle hObject,
HandleFlags dwMask,
int dwFlags);
[DllImport("kernel32.dll", SetLastError = true)]
- public static extern bool InitializeProcThreadAttributeList(
- IntPtr lpAttributeList,
- int dwAttributeCount,
- int dwFlags,
- ref int lpSize);
-
- [DllImport("kernel32.dll", SetLastError = true)]
- public static extern bool UpdateProcThreadAttribute(
- IntPtr lpAttributeList,
- uint dwFlags,
- IntPtr Attribute,
- IntPtr lpValue,
- IntPtr cbSize,
- IntPtr lpPreviousValue,
- IntPtr lpReturnSize);
-
- [DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetExitCodeProcess(
IntPtr hProcess,
out uint lpExitCode);
- [DllImport("kernel32.dll", SetLastError = true)]
- public static extern bool CloseHandle(
- IntPtr hObject);
-
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint SearchPath(
string lpPath,
@@ -220,7 +199,7 @@ namespace Ansible
public static CommandResult RunCommand(string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, string stdinInput, IDictionary environment)
{
- UInt32 startup_flags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | EXTENDED_STARTUPINFO_PRESENT;
+ UInt32 startup_flags = CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT;
STARTUPINFOEX si = new STARTUPINFOEX();
si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES;
@@ -228,7 +207,7 @@ namespace Ansible
pipesec.bInheritHandle = true;
// Create the stdout, stderr and stdin pipes used in the process and add to the startupInfo
- IntPtr stdout_read, stdout_write, stderr_read, stderr_write, stdin_read, stdin_write = IntPtr.Zero;
+ SafeFileHandle stdout_read, stdout_write, stderr_read, stderr_write, stdin_read, stdin_write;
if (!CreatePipe(out stdout_read, out stdout_write, pipesec, 0))
throw new Win32Exception("STDOUT pipe setup failed");
if (!SetHandleInformation(stdout_read, HandleFlags.INHERIT, 0))
@@ -248,37 +227,9 @@ namespace Ansible
si.startupInfo.hStdError = stderr_write;
si.startupInfo.hStdInput = stdin_read;
- // Handle the inheritance for the pipes so the process can access them
- Int32 buf_sz = 0;
- if (!InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref buf_sz))
- {
- int last_err = Marshal.GetLastWin32Error();
- if (last_err != 122) // ERROR_INSUFFICIENT_BUFFER
- throw new Win32Exception(last_err, "Attribute list size query failed");
- }
- si.lpAttributeList = Marshal.AllocHGlobal(buf_sz);
- if (!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, ref buf_sz))
- throw new Win32Exception("Attribute list init failed");
-
-
- IntPtr[] handles_to_inherit = new IntPtr[3];
- handles_to_inherit[0] = stdin_read;
- handles_to_inherit[1] = stdout_write;
- handles_to_inherit[2] = stderr_write;
- GCHandle pinned_handles = GCHandle.Alloc(handles_to_inherit, GCHandleType.Pinned);
-
- if (!UpdateProcThreadAttribute(si.lpAttributeList, 0,
- (IntPtr)0x20002, // PROC_THREAD_ATTRIBUTE_HANDLE_LIST
- pinned_handles.AddrOfPinnedObject(),
- (IntPtr)(Marshal.SizeOf(typeof(IntPtr)) * handles_to_inherit.Length),
- IntPtr.Zero, IntPtr.Zero))
- {
- throw new Win32Exception("Attribute list update failed");
- }
-
// Setup the stdin buffer
UTF8Encoding utf8_encoding = new UTF8Encoding(false);
- FileStream stdin_fs = new FileStream(stdin_write, FileAccess.Write, true, 32768);
+ FileStream stdin_fs = new FileStream(stdin_write, FileAccess.Write, 32768);
StreamWriter stdin = new StreamWriter(stdin_fs, utf8_encoding, 32768);
// If lpCurrentDirectory is set to null in PS it will be an empty
@@ -288,7 +239,7 @@ namespace Ansible
StringBuilder environmentString = null;
- if(environment != null && environment.Count > 0)
+ if (environment != null && environment.Count > 0)
{
environmentString = new StringBuilder();
foreach (DictionaryEntry kv in environment)
@@ -320,12 +271,12 @@ namespace Ansible
}
// Setup the output buffers and get stdout/stderr
- FileStream stdout_fs = new FileStream(stdout_read, FileAccess.Read, true, 4096);
+ FileStream stdout_fs = new FileStream(stdout_read, FileAccess.Read, 4096);
StreamReader stdout = new StreamReader(stdout_fs, utf8_encoding, true, 4096);
- FileStream stderr_fs = new FileStream(stderr_read, FileAccess.Read, true, 4096);
+ stdout_write.Close();
+ FileStream stderr_fs = new FileStream(stderr_read, FileAccess.Read, 4096);
StreamReader stderr = new StreamReader(stderr_fs, utf8_encoding, true, 4096);
- CloseHandle(stdout_write);
- CloseHandle(stderr_write);
+ stderr_write.Close();
stdin.WriteLine(stdinInput);
stdin.Close();
@@ -384,7 +335,7 @@ Function Load-CommandUtils {
# [Ansible.CommandUtil]::RunCommand(string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, string stdinInput, string environmentBlock)
#
# there are also numerous P/Invoke methods that can be called if you are feeling adventurous
- Add-Type -TypeDefinition $process_util -IgnoreWarnings -Debug:$false
+ Add-Type -TypeDefinition $process_util
}
Function Get-ExecutablePath($executable, $directory) {
diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.FileUtil.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.FileUtil.psm1
index 2e90559..727da96 100644
--- a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.FileUtil.psm1
+++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.FileUtil.psm1
@@ -17,7 +17,7 @@ Function Test-AnsiblePath {
# Replacement for Test-Path
try {
$file_attributes = [System.IO.File]::GetAttributes($Path)
- } catch [System.IO.FileNotFoundException] {
+ } catch [System.IO.FileNotFoundException], [System.IO.DirectoryNotFoundException] {
return $false
}
diff --git a/lib/ansible/module_utils/vmware.py b/lib/ansible/module_utils/vmware.py
index 2913fe2..829c2f8 100644
--- a/lib/ansible/module_utils/vmware.py
+++ b/lib/ansible/module_utils/vmware.py
@@ -34,6 +34,7 @@ except ImportError:
from ansible.module_utils._text import to_text
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.six import integer_types, iteritems, string_types
+from ansible.module_utils.basic import env_fallback
class TaskError(Exception):
@@ -290,6 +291,7 @@ def gather_vm_facts(content, vm):
'hw_guest_ha_state': None,
'hw_is_template': vm.config.template,
'hw_folder': None,
+ 'instance_uuid': vm.config.instanceUuid,
'guest_tools_status': _get_vm_prop(vm, ('guest', 'toolsRunningStatus')),
'guest_tools_version': _get_vm_prop(vm, ('guest', 'toolsVersion')),
'guest_question': vm.summary.runtime.question,
@@ -304,8 +306,14 @@ def gather_vm_facts(content, vm):
# facts that may or may not exist
if vm.summary.runtime.host:
- host = vm.summary.runtime.host
- facts['hw_esxi_host'] = host.summary.config.name
+ try:
+ host = vm.summary.runtime.host
+ facts['hw_esxi_host'] = host.summary.config.name
+ except vim.fault.NoPermission:
+ # User does not have read permission for the host system,
+ # proceed without this value. This value does not contribute or hamper
+ # provisioning or power management operations.
+ pass
if vm.summary.runtime.dasVmProtection:
facts['hw_guest_ha_state'] = vm.summary.runtime.dasVmProtection.dasProtected
@@ -432,13 +440,27 @@ def list_snapshots(vm):
def vmware_argument_spec():
-
return dict(
- hostname=dict(type='str', required=True),
- username=dict(type='str', aliases=['user', 'admin'], required=True),
- password=dict(type='str', aliases=['pass', 'pwd'], required=True, no_log=True),
- port=dict(type='int', default=443),
- validate_certs=dict(type='bool', required=False, default=True),
+ hostname=dict(type='str',
+ required=False,
+ fallback=(env_fallback, ['VMWARE_HOST']),
+ ),
+ username=dict(type='str',
+ aliases=['user', 'admin'],
+ required=False,
+ fallback=(env_fallback, ['VMWARE_USER'])),
+ password=dict(type='str',
+ aliases=['pass', 'pwd'],
+ required=False,
+ no_log=True,
+ fallback=(env_fallback, ['VMWARE_PASSWORD'])),
+ port=dict(type='int',
+ default=443,
+ fallback=(env_fallback, ['VMWARE_PORT'])),
+ validate_certs=dict(type='bool',
+ required=False,
+ default=True,
+ fallback=(env_fallback, ['VMWARE_VALIDATE_CERTS'])),
)
@@ -446,9 +468,24 @@ def connect_to_api(module, disconnect_atexit=True):
hostname = module.params['hostname']
username = module.params['username']
password = module.params['password']
- port = module.params['port'] or 443
+ port = module.params.get('port', 443)
validate_certs = module.params['validate_certs']
+ if not hostname:
+ module.fail_json(msg="Hostname parameter is missing."
+ " Please specify this parameter in task or"
+ " export environment variable like 'export VMWARE_HOST=ESXI_HOSTNAME'")
+
+ if not username:
+ module.fail_json(msg="Username parameter is missing."
+ " Please specify this parameter in task or"
+ " export environment variable like 'export VMWARE_USER=ESXI_USERNAME'")
+
+ if not password:
+ module.fail_json(msg="Password parameter is missing."
+ " Please specify this parameter in task or"
+ " export environment variable like 'export VMWARE_PASSWORD=ESXI_PASSWORD'")
+
if validate_certs and not hasattr(ssl, 'SSLContext'):
module.fail_json(msg='pyVim does not support changing verification mode with python < 2.7.9. Either update '
'python or use validate_certs=false.')
diff --git a/lib/ansible/modules/cloud/amazon/aws_s3.py b/lib/ansible/modules/cloud/amazon/aws_s3.py
index 28e9261..84abeb4 100644
--- a/lib/ansible/modules/cloud/amazon/aws_s3.py
+++ b/lib/ansible/modules/cloud/amazon/aws_s3.py
@@ -27,6 +27,8 @@ description:
- This module allows the user to manage S3 buckets and the objects within them. Includes support for creating and
deleting both objects and buckets, retrieving objects as files or strings and generating download links.
This module has a dependency on boto3 and botocore.
+notes:
+ - In 2.4, this module has been renamed from M(s3) into M(aws_s3).
version_added: "1.1"
options:
aws_access_key:
diff --git a/lib/ansible/modules/cloud/amazon/ec2.py b/lib/ansible/modules/cloud/amazon/ec2.py
index cb440eb..320887c 100644
--- a/lib/ansible/modules/cloud/amazon/ec2.py
+++ b/lib/ansible/modules/cloud/amazon/ec2.py
@@ -620,15 +620,15 @@ EXAMPLES = '''
'''
-import traceback
import time
+import traceback
from ast import literal_eval
-from ansible.module_utils.six import get_function_code, string_types
-from ansible.module_utils._text import to_text
+from distutils.version import LooseVersion
+
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ec2 import get_aws_connection_info, ec2_argument_spec, ec2_connect
-from distutils.version import LooseVersion
-from ansible.module_utils.six import string_types
+from ansible.module_utils.six import get_function_code, string_types
+from ansible.module_utils._text import to_bytes, to_text
try:
import boto.ec2
@@ -1121,7 +1121,7 @@ def create_instances(module, ec2, vpc, override_count=None):
'instance_type': instance_type,
'kernel_id': kernel,
'ramdisk_id': ramdisk,
- 'user_data': user_data}
+ 'user_data': to_bytes(user_data, errors='surrogate_or_strict')}
if ebs_optimized:
params['ebs_optimized'] = ebs_optimized
diff --git a/lib/ansible/modules/cloud/amazon/ec2_asg.py b/lib/ansible/modules/cloud/amazon/ec2_asg.py
index 517f9bc..6184083 100644
--- a/lib/ansible/modules/cloud/amazon/ec2_asg.py
+++ b/lib/ansible/modules/cloud/amazon/ec2_asg.py
@@ -1145,6 +1145,7 @@ def replace(connection):
desired_capacity = module.params.get('desired_capacity')
lc_check = module.params.get('lc_check')
replace_instances = module.params.get('replace_instances')
+ replace_all_instances = module.params.get('replace_all_instances')
as_group = describe_autoscaling_groups(connection, group_name)[0]
if desired_capacity is None:
@@ -1153,6 +1154,10 @@ def replace(connection):
wait_for_new_inst(connection, group_name, wait_timeout, as_group['MinSize'], 'viable_instances')
props = get_properties(as_group)
instances = props['instances']
+ if replace_all_instances:
+ # If replacing all instances, then set replace_instances to current set
+ # This allows replace_instances and replace_all_instances to behave same
+ replace_instances = instances
if replace_instances:
instances = replace_instances
# check to see if instances are replaceable if checking launch configs
@@ -1189,7 +1194,7 @@ def replace(connection):
as_group = describe_autoscaling_groups(connection, group_name)[0]
update_size(connection, as_group, max_size + batch_size, min_size + batch_size, desired_capacity + batch_size)
- wait_for_new_inst(connection, group_name, wait_timeout, as_group['MinSize'], 'viable_instances')
+ wait_for_new_inst(connection, group_name, wait_timeout, as_group['MinSize'] + batch_size, 'viable_instances')
wait_for_elb(connection, group_name)
wait_for_target_group(connection, group_name)
as_group = describe_autoscaling_groups(connection, group_name)[0]
@@ -1368,6 +1373,12 @@ def wait_for_new_inst(connection, group_name, wait_timeout, desired_size, prop):
return props
+def asg_exists(connection):
+ group_name = module.params.get('name')
+ as_group = describe_autoscaling_groups(connection, group_name)
+ return bool(len(as_group))
+
+
def main():
argument_spec = ec2_argument_spec()
argument_spec.update(
@@ -1425,13 +1436,16 @@ def main():
endpoint=ec2_url,
**aws_connect_params)
changed = create_changed = replace_changed = False
+ exists = asg_exists(connection)
if state == 'present':
create_changed, asg_properties = create_autoscaling_group(connection)
elif state == 'absent':
changed = delete_autoscaling_group(connection)
module.exit_json(changed=changed)
- if replace_all_instances or replace_instances:
+
+ # Only replace instances if asg existed at start of call
+ if exists and (replace_all_instances or replace_instances):
replace_changed, asg_properties = replace(connection)
if create_changed or replace_changed:
changed = True
diff --git a/lib/ansible/modules/cloud/amazon/ec2_group.py b/lib/ansible/modules/cloud/amazon/ec2_group.py
index 4370298..f11b4f7 100644
--- a/lib/ansible/modules/cloud/amazon/ec2_group.py
+++ b/lib/ansible/modules/cloud/amazon/ec2_group.py
@@ -941,7 +941,7 @@ def main():
# If rule already exists, don't later delete it
changed, ip_permission = authorize_ip("out", changed, client, group, groupRules, ipv6,
ip_permission, module, rule, "ipv6")
- elif vpc_id is not None:
+ elif 'VpcId' in group:
# when no egress rules are specified and we're in a VPC,
# we add in a default allow all out rule, which was the
# default behavior before egress rules were added
diff --git a/lib/ansible/modules/cloud/azure/azure_rm_image.py b/lib/ansible/modules/cloud/azure/azure_rm_image.py
index 0512d5d..80f4778 100644
--- a/lib/ansible/modules/cloud/azure/azure_rm_image.py
+++ b/lib/ansible/modules/cloud/azure/azure_rm_image.py
@@ -135,13 +135,12 @@ class AzureRMImage(AzureRMModuleBase):
self.source = None
self.data_disk_sources = None
self.os_type = None
- self.tags = None
super(AzureRMImage, self).__init__(self.module_arg_spec, supports_check_mode=True, required_if=required_if)
def exec_module(self, **kwargs):
- for key in self.module_arg_spec:
+ for key in list(self.module_arg_spec.keys()) + ['tags']:
setattr(self, key, kwargs[key])
results = None
@@ -158,7 +157,11 @@ class AzureRMImage(AzureRMModuleBase):
if image:
self.check_provisioning_state(image, self.state)
results = image.id
- # update is not supported
+ # update is not supported except for tags
+ update_tags, tags = self.update_tags(image.tags)
+ if update_tags:
+ changed = True
+ self.tags = tags
if self.state == 'absent':
changed = True
# the image does not exist and create a new one
@@ -176,7 +179,7 @@ class AzureRMImage(AzureRMModuleBase):
if vm:
if self.data_disk_sources:
self.fail('data_disk_sources is not allowed when capturing image from vm')
- image_instance = self.compute_models.Image(self.location, source_virtual_machine=self.compute_models.SubResource(vm.id))
+ image_instance = self.compute_models.Image(self.location, source_virtual_machine=self.compute_models.SubResource(vm.id), tags=self.tags)
else:
if not self.os_type:
self.fail('os_type is required to create the image')
diff --git a/lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py b/lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py
index 13ac61a..335526b 100644
--- a/lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py
+++ b/lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py
@@ -173,68 +173,68 @@ author:
EXAMPLES = '''
- name: Create a network interface with minimal parameters
azure_rm_networkinterface:
- name: nic001
- resource_group: Testing
- virtual_network_name: vnet001
- subnet_name: subnet001
- ip_configurations:
- name: ipconfig1
- public_ip_address_name: publicip001
- primary: True
+ name: nic001
+ resource_group: Testing
+ virtual_network_name: vnet001
+ subnet_name: subnet001
+ ip_configurations:
+ - name: ipconfig1
+ public_ip_address_name: publicip001
+ primary: True
- name: Create a network interface with private IP address only (no Public IP)
azure_rm_networkinterface:
- name: nic001
- resource_group: Testing
- virtual_network_name: vnet001
- subnet_name: subnet001
- ip_configurations:
- name: ipconfig1
- primary: True
+ name: nic001
+ resource_group: Testing
+ virtual_network_name: vnet001
+ subnet_name: subnet001
+ ip_configurations:
+ - name: ipconfig1
+ primary: True
- name: Create a network interface for use in a Windows host (opens RDP port) with custom RDP port
azure_rm_networkinterface:
- name: nic002
- resource_group: Testing
- virtual_network_name: vnet001
- subnet_name: subnet001
- os_type: Windows
- rdp_port: 3399
- ip_configurations:
- name: ipconfig1
- public_ip_address_name: publicip001
- primary: True
+ name: nic002
+ resource_group: Testing
+ virtual_network_name: vnet001
+ subnet_name: subnet001
+ os_type: Windows
+ rdp_port: 3399
+ ip_configurations:
+ - name: ipconfig1
+ public_ip_address_name: publicip001
+ primary: True
- name: Create a network interface using existing security group and public IP
azure_rm_networkinterface:
- name: nic003
- resource_group: Testing
- virtual_network_name: vnet001
- subnet_name: subnet001
- security_group_name: secgroup001
- ip_configurations:
- name: ipconfig1
- public_ip_address_name: publicip001
- primary: True
+ name: nic003
+ resource_group: Testing
+ virtual_network_name: vnet001
+ subnet_name: subnet001
+ security_group_name: secgroup001
+ ip_configurations:
+ - name: ipconfig1
+ public_ip_address_name: publicip001
+ primary: True
- name: Create a network with mutilple ip configurations
azure_rm_networkinterface:
- name: nic004
- resource_group: Testing
- subnet_name: subnet001
- virtual_network_name: vnet001
- security_group_name: secgroup001
- ip_configurations:
- - name: ipconfig1
- public_ip_address_name: publicip001
- primary: True
- - name: ipconfig2
+ name: nic004
+ resource_group: Testing
+ subnet_name: subnet001
+ virtual_network_name: vnet001
+ security_group_name: secgroup001
+ ip_configurations:
+ - name: ipconfig1
+ public_ip_address_name: publicip001
+ primary: True
+ - name: ipconfig2
- name: Delete network interface
azure_rm_networkinterface:
- resource_group: Testing
- name: nic003
- state: absent
+ resource_group: Testing
+ name: nic003
+ state: absent
'''
RETURN = '''
@@ -536,6 +536,10 @@ class AzureRMNetworkInterface(AzureRMModuleBase):
def get_or_create_public_ip_address(self, ip_config):
name = ip_config.get('public_ip_address_name')
+
+ if not (self.public_ip and name):
+ return None
+
pip = self.get_public_ip_address(name)
if not pip:
params = self.network_models.PublicIPAddress(
diff --git a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py
index c4fa68e..4ba3e72 100644
--- a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py
+++ b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py
@@ -26,8 +26,9 @@ description:
- Create, update, stop and start a virtual machine. Provide an existing storage account and network interface or
allow the module to create these for you. If you choose not to provide a network interface, the resource group
must contain a virtual network with at least one subnet.
- - Currently requires an image found in the Azure Marketplace. Use azure_rm_virtualmachineimage_facts module
- to discover the publisher, offer, sku and version of a particular image.
+ - Before Ansible 2.5, this required an image found in the Azure Marketplace which can be discovered with
+ M(azure_rm_virtualmachineimage_facts). In Ansible 2.5 and newer, custom images can be used as well, see the
+ examples for more details.
options:
resource_group:
diff --git a/lib/ansible/modules/cloud/cloudstack/cs_configuration.py b/lib/ansible/modules/cloud/cloudstack/cs_configuration.py
index cc5659d..2e7e92c 100644
--- a/lib/ansible/modules/cloud/cloudstack/cs_configuration.py
+++ b/lib/ansible/modules/cloud/cloudstack/cs_configuration.py
@@ -228,7 +228,9 @@ class AnsibleCloudStackConfiguration(AnsibleCloudStack):
configurations = self.query_api('listConfigurations', **args)
if not configurations:
self.module.fail_json(msg="Configuration %s not found." % args['name'])
- configuration = configurations['configuration'][0]
+ for config in configurations['configuration']:
+ if args['name'] == config['name']:
+ configuration = config
return configuration
def get_value(self):
diff --git a/lib/ansible/modules/cloud/cloudstack/cs_vpc_offering.py b/lib/ansible/modules/cloud/cloudstack/cs_vpc_offering.py
index c2e159b..67ce8bd 100644
--- a/lib/ansible/modules/cloud/cloudstack/cs_vpc_offering.py
+++ b/lib/ansible/modules/cloud/cloudstack/cs_vpc_offering.py
@@ -149,7 +149,9 @@ class AnsibleCloudStackVPCOffering(AnsibleCloudStack):
vo = self.query_api('listVPCOfferings', **args)
if vo:
- self.vpc_offering = vo['vpcoffering'][0]
+ for vpc_offer in vo['vpcoffering']:
+ if args['name'] == vpc_offer['name']:
+ self.vpc_offering = vpc_offer
return self.vpc_offering
diff --git a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_domain.py b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_domain.py
index 3e0f901..f6df174 100644
--- a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_domain.py
+++ b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_domain.py
@@ -143,7 +143,8 @@ class DoManager(DigitalOceanHelper, object):
def edit_domain_record(self):
params = {'name': self.domain_name}
resp = self.put('domains/%s/records/%s' % (self.domain_name, self.domain_id), data=params)
- return resp['domain_record']
+ status, json = self.jsonify(resp)
+ return json['domain_record']
def core(module):
diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_cluster.py b/lib/ansible/modules/cloud/ovirt/ovirt_cluster.py
index e282c03..9b61636 100644
--- a/lib/ansible/modules/cloud/ovirt/ovirt_cluster.py
+++ b/lib/ansible/modules/cloud/ovirt/ovirt_cluster.py
@@ -485,7 +485,9 @@ class ClustersModule(BaseModule):
name=self.param('network'),
) if self.param('network') else None,
cpu=otypes.Cpu(
- architecture=self.param('cpu_arch'),
+ architecture=otypes.Architecture(
+ self.param('cpu_arch')
+ ) if self.param('cpu_arch') else None,
type=self.param('cpu_type'),
) if (
self.param('cpu_arch') or self.param('cpu_type')
diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_host_networks.py b/lib/ansible/modules/cloud/ovirt/ovirt_host_networks.py
index feff495..d55bdda 100644
--- a/lib/ansible/modules/cloud/ovirt/ovirt_host_networks.py
+++ b/lib/ansible/modules/cloud/ovirt/ovirt_host_networks.py
@@ -57,7 +57,7 @@ options:
- "C(name) - Name of the logical network to be assigned to bond or interface."
- "C(boot_protocol) - Boot protocol one of the I(none), I(static) or I(dhcp)."
- "C(address) - IP address in case of I(static) boot protocol is used."
- - "C(prefix) - Routing prefix in case of I(static) boot protocol is used."
+ - "C(netmask) - Subnet mask in case of I(static) boot protocol is used."
- "C(gateway) - Gateway in case of I(static) boot protocol is used."
- "C(version) - IP version. Either v4 or v6. Default is v4."
labels:
@@ -92,7 +92,7 @@ EXAMPLES = '''
- name: myvlan
boot_protocol: static
address: 1.2.3.4
- prefix: 24
+ netmask: 255.255.255.0
gateway: 1.2.3.4
version: v4
@@ -160,8 +160,56 @@ from ansible.module_utils.ovirt import (
)
+def get_mode_type(mode_number):
+ """
+ Adaptive transmit load balancing (balance-tlb): mode=1 miimon=100
+ Dynamic link aggregation (802.3ad): mode=2 miimon=100
+ Load balance (balance-xor): mode=3 miimon=100
+ Active-Backup: mode=4 miimon=100 xmit_hash_policy=2
+ """
+ options = []
+ if mode_number is None:
+ return options
+
+ def get_type_name(mode_number):
+ """
+ We need to maintain this type strings, for the __compare_options method,
+ for easier comparision.
+ """
+ return [
+ 'Active-Backup',
+ 'Load balance (balance-xor)',
+ None,
+ 'Dynamic link aggregation (802.3ad)',
+ ][mode_number]
+
+ try:
+ mode_number = int(mode_number)
+ if mode_number >= 1 and mode_number <= 4:
+ if mode_number == 4:
+ options.append(otypes.Option(name='xmit_hash_policy', value='2'))
+
+ options.append(otypes.Option(name='miimon', value='100'))
+ options.append(
+ otypes.Option(
+ name='mode',
+ type=get_type_name(mode_number - 1),
+ value=str(mode_number)
+ )
+ )
+ else:
+ options.append(otypes.Option(name='mode', value=str(mode_number)))
+ except ValueError:
+ raise Exception("Bond mode must be a number.")
+
+ return options
+
+
class HostNetworksModule(BaseModule):
+ def __compare_options(self, new_options, old_options):
+ return sorted(get_dict_of_struct(opt) for opt in new_options) != sorted(get_dict_of_struct(opt) for opt in old_options)
+
def build_entity(self):
return otypes.Host()
@@ -180,8 +228,8 @@ class HostNetworksModule(BaseModule):
if not equal(network.get('gateway'), ip.ip.gateway):
ip.ip.gateway = network.get('gateway')
changed = True
- if not equal(network.get('prefix'), sum([bin(int(x)).count('1') for x in ip.ip.netmask.split('.')]) if ip.ip.netmask else None):
- ip.ip.netmask = str(network.get('prefix'))
+ if not equal(network.get('netmask'), ip.ip.netmask):
+ ip.ip.netmask = network.get('netmask')
changed = True
if changed:
@@ -202,17 +250,16 @@ class HostNetworksModule(BaseModule):
# Check if bond configuration should be updated:
if bond:
- update = not (
- equal(str(bond.get('mode')), nic.bonding.options[0].value) and
- equal(
- sorted(bond.get('interfaces')) if bond.get('interfaces') else None,
- sorted(get_link_name(self._connection, s) for s in nic.bonding.slaves)
- )
+ update = self.__compare_options(get_mode_type(bond.get('mode')), getattr(nic.bonding, 'options', []))
+ update = update or not equal(
+ sorted(bond.get('interfaces')) if bond.get('interfaces') else None,
+ sorted(get_link_name(self._connection, s) for s in nic.bonding.slaves)
)
# Check if labels need to be updated on interface/bond:
if labels:
net_labels = nic_service.network_labels_service().list()
+ # If any lables which user passed aren't assigned, relabel the interface:
if sorted(labels) != sorted([lbl.id for lbl in net_labels]):
return True
@@ -283,29 +330,46 @@ def main():
labels = module.params['labels']
nic_name = bond.get('name') if bond else module.params['interface']
- nics_service = hosts_service.host_service(host.id).nics_service()
+ host_service = hosts_service.host_service(host.id)
+ nics_service = host_service.nics_service()
nic = search_by_name(nics_service, nic_name)
+ network_names = [network['name'] for network in networks or []]
state = module.params['state']
if (
state == 'present' and
(nic is None or host_networks_module.has_update(nics_service.service(nic.id)))
):
+ # Remove networks which are attached to different interface then user want:
+ attachments_service = host_service.network_attachments_service()
+
+ # Append attachment ID to network if needs update:
+ for a in attachments_service.list():
+ current_network_name = get_link_name(connection, a.network)
+ if current_network_name in network_names:
+ for n in networks:
+ if n['name'] == current_network_name:
+ n['id'] = a.id
+
+ # Check if we have to break some bonds:
+ removed_bonds = []
+ if nic is not None:
+ for host_nic in nics_service.list():
+ if host_nic.bonding and nic.id in [slave.id for slave in host_nic.bonding.slaves]:
+ removed_bonds.append(otypes.HostNic(id=host_nic.id))
+
+ # Assign the networks:
host_networks_module.action(
entity=host,
action='setup_networks',
post_action=host_networks_module._action_save_configuration,
check_connectivity=module.params['check'],
+ removed_bonds=removed_bonds if removed_bonds else None,
modified_bonds=[
otypes.HostNic(
name=bond.get('name'),
bonding=otypes.Bonding(
- options=[
- otypes.Option(
- name="mode",
- value=str(bond.get('mode')),
- )
- ],
+ options=get_mode_type(bond.get('mode')),
slaves=[
otypes.HostNic(name=i) for i in bond.get('interfaces', [])
],
@@ -322,6 +386,7 @@ def main():
] if labels else None,
modified_network_attachments=[
otypes.NetworkAttachment(
+ id=network.get('id'),
network=otypes.Network(
name=network['name']
) if network['name'] else None,
@@ -353,7 +418,6 @@ def main():
attachments = attachments_service.list()
attached_labels = set([str(lbl.id) for lbl in nic_service.network_labels_service().list()])
if networks:
- network_names = [network['name'] for network in networks]
attachments = [
attachment for attachment in attachments
if get_link_name(connection, attachment.network) in network_names
@@ -374,7 +438,7 @@ def main():
] if bond else None,
removed_labels=[
otypes.NetworkLabel(id=str(name)) for name in labels
- ],
+ ] if labels else None,
removed_network_attachments=list(attachments),
)
diff --git a/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py b/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py
index bb2dfcb..3b863ec 100644
--- a/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py
+++ b/lib/ansible/modules/cloud/ovirt/ovirt_hosts.py
@@ -532,7 +532,7 @@ def main():
action='fence',
action_condition=lambda h: h.status == hoststate.DOWN,
wait_condition=lambda h: h.status in [hoststate.UP, hoststate.MAINTENANCE],
- fail_condition=failed_state,
+ fail_condition=hosts_module.failed_state_after_reinstall,
fence_type='start',
)
elif state == 'stopped':
@@ -553,7 +553,7 @@ def main():
ret = hosts_module.action(
action='fence',
wait_condition=lambda h: h.status == hoststate.UP,
- fail_condition=failed_state,
+ fail_condition=hosts_module.failed_state_after_reinstall,
fence_type='restart',
)
elif state == 'reinstalled':
diff --git a/lib/ansible/modules/cloud/vmware/vcenter_license.py b/lib/ansible/modules/cloud/vmware/vcenter_license.py
index e324b66..8cef1d5 100644
--- a/lib/ansible/modules/cloud/vmware/vcenter_license.py
+++ b/lib/ansible/modules/cloud/vmware/vcenter_license.py
@@ -70,7 +70,7 @@ EXAMPLES = r'''
delegate_to: localhost
- name: Remove an (unused) vCenter license
- vmware_license:
+ vcenter_license:
hostname: '{{ vcenter_hostname }}'
username: '{{ vcenter_username }}'
password: '{{ vcenter_password }}'
diff --git a/lib/ansible/modules/cloud/vmware/vmware_cfg_backup.py b/lib/ansible/modules/cloud/vmware/vmware_cfg_backup.py
index 1e3f301..58e965f 100644
--- a/lib/ansible/modules/cloud/vmware/vmware_cfg_backup.py
+++ b/lib/ansible/modules/cloud/vmware/vmware_cfg_backup.py
@@ -31,7 +31,7 @@ requirements:
options:
esxi_hostname:
description:
- - Name of ESXi server.
+ - Name of ESXi server. This is required only if authentication against a vCenter is done.
required: False
dest:
description:
@@ -51,17 +51,17 @@ extends_documentation_fragment: vmware.documentation
'''
EXAMPLES = '''
-# save the ESXi configuration locally
+# save the ESXi configuration locally by authenticating directly against the ESXi host
- name: ESXI backup test
local_action:
module: vmware_cfg_backup
- hostname: esxi_host
+ hostname: esxi_hostname
username: user
password: pass
state: saved
dest: /tmp/
-# save the ESXi configuration locally for specific ESXi
+# save the ESXi configuration locally by authenticating against the vCenter and selecting the ESXi host
- name: ESXI backup test
local_action:
module: vmware_cfg_backup
@@ -133,7 +133,7 @@ class VMwareConfigurationBackup(PyVmomi):
self.module.fail_json(msg="Source file {} does not exist".format(self.src))
url = self.host.configManager.firmwareSystem.QueryFirmwareConfigUploadURL()
- url = url.replace('*', self.hostname)
+ url = url.replace('*', self.host.name)
# find manually the url if there is a redirect because urllib2 -per RFC- doesn't do automatic redirects for PUT requests
try:
request = open_url(url=url, method='HEAD', validate_certs=self.validate_certs)
@@ -169,7 +169,7 @@ class VMwareConfigurationBackup(PyVmomi):
def save_configuration(self):
url = self.host.configManager.firmwareSystem.BackupFirmwareConfiguration()
- url = url.replace('*', self.hostname)
+ url = url.replace('*', self.host.name)
if os.path.isdir(self.dest):
filename = url.rsplit('/', 1)[1]
self.dest = os.path.join(self.dest, filename)
diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest.py b/lib/ansible/modules/cloud/vmware/vmware_guest.py
index 712a5bb..55cbdc9 100644
--- a/lib/ansible/modules/cloud/vmware/vmware_guest.py
+++ b/lib/ansible/modules/cloud/vmware/vmware_guest.py
@@ -209,7 +209,7 @@ options:
- ' - C(device_type) (string): Virtual network device (one of C(e1000), C(e1000e), C(pcnet32), C(vmxnet2), C(vmxnet3) (default), C(sriov)).'
- ' - C(mac) (string): Customize mac address.'
- 'Optional parameters per entry (used for OS customization):'
- - ' - C(type) (string): Type of IP assignment (either C(dhcp) or C(static)).'
+ - ' - C(type) (string): Type of IP assignment (either C(dhcp) or C(static)). C(dhcp) is default.'
- ' - C(ip) (string): Static IP address (implies C(type: static)).'
- ' - C(netmask) (string): Static netmask required for C(ip).'
- ' - C(gateway) (string): Static gateway.'
@@ -936,6 +936,9 @@ class PyVmomiHelper(PyVmomi):
# network type as 'static'
if 'ip' in network or 'netmask' in network:
network['type'] = 'static'
+ else:
+ # User wants network type as 'dhcp'
+ network['type'] = 'dhcp'
if network.get('type') == 'static':
if 'ip' in network and 'netmask' not in network:
diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest_find.py b/lib/ansible/modules/cloud/vmware/vmware_guest_find.py
index 6dd360d..17cb077 100644
--- a/lib/ansible/modules/cloud/vmware/vmware_guest_find.py
+++ b/lib/ansible/modules/cloud/vmware/vmware_guest_find.py
@@ -30,22 +30,21 @@ requirements:
- PyVmomi
options:
name:
- description:
- - Name of the VM to work with.
- - This is required if uuid is not supplied.
+ description:
+ - Name of the VM to work with.
+ - This is required if C(uuid) parameter is not supplied.
uuid:
- description:
- - UUID of the instance to manage if known, this is VMware's BIOS UUID.
- - This is required if name is not supplied.
+ description:
+ - UUID of the instance to manage if known, this is VMware's BIOS UUID.
+ - This is required if C(name) parameter is not supplied.
datacenter:
- description:
- - Destination datacenter for the find operation.
- - Deprecated in 2.5, will be removed in 2.9 release.
- required: True
+ description:
+ - Destination datacenter for the find operation.
+ - Deprecated in 2.5, will be removed in 2.9 release.
extends_documentation_fragment: vmware.documentation
'''
-EXAMPLES = '''
+EXAMPLES = r'''
- name: Find Guest's Folder using name
vmware_guest_find:
hostname: 192.168.1.209
@@ -70,6 +69,9 @@ folders:
description: List of folders for user specified virtual machine
returned: on success
type: list
+ sample: [
+ '/DC0/vm',
+ ]
"""
@@ -77,9 +79,7 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.vmware import PyVmomi, get_all_objs, vmware_argument_spec
-
try:
- import pyVmomi
from pyVmomi import vim
except ImportError:
pass
@@ -88,8 +88,6 @@ except ImportError:
class PyVmomiHelper(PyVmomi):
def __init__(self, module):
super(PyVmomiHelper, self).__init__(module)
- self.datacenter = None
- self.folders = None
self.name = self.params['name']
self.uuid = self.params['uuid']
@@ -114,11 +112,13 @@ def main():
argument_spec.update(
name=dict(type='str'),
uuid=dict(type='str'),
- datacenter=dict(removed_in_version=2.9, type='str', required=True)
+ datacenter=dict(removed_in_version=2.9, type='str')
)
module = AnsibleModule(argument_spec=argument_spec,
- required_one_of=[['name', 'uuid']])
+ required_one_of=[['name', 'uuid']],
+ mutually_exclusive=[['name', 'uuid']],
+ )
pyv = PyVmomiHelper(module)
# Check if the VM exists before continuing
diff --git a/lib/ansible/modules/clustering/consul_kv.py b/lib/ansible/modules/clustering/consul_kv.py
index 967dadc..f284b3d 100644
--- a/lib/ansible/modules/clustering/consul_kv.py
+++ b/lib/ansible/modules/clustering/consul_kv.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
#
# (c) 2015, Steve Gargan <steve.gargan@gmail.com>
+# (c) 2017, 2018 Genome Research Ltd.
# 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
@@ -28,7 +29,8 @@ requirements:
- requests
version_added: "2.0"
author:
-- Steve Gargan (@sgargan)
+ - Steve Gargan (@sgargan)
+ - Colin Nolan (@colin-nolan)
options:
state:
description:
@@ -122,6 +124,8 @@ EXAMPLES = '''
state: acquire
'''
+from ansible.module_utils._text import to_text
+
try:
import consul
from requests.exceptions import ConnectionError
@@ -132,6 +136,27 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
+def _has_value_changed(consul_client, key, target_value):
+ """
+ Uses the given Consul client to determine if the value associated to the given key is different to the given target
+ value.
+ :param consul_client: Consul connected client
+ :param key: key in Consul
+ :param target_value: value to be associated to the key
+ :return: tuple where the first element is the value of the "X-Consul-Index" header and the second is `True` if the
+ value has changed (i.e. the stored value is not the target value)
+ """
+ index, existing = consul_client.kv.get(key)
+ if not existing:
+ return index, True
+ try:
+ changed = to_text(existing['Value'], errors='surrogate_or_strict') != target_value
+ return index, changed
+ except UnicodeError:
+ # Existing value was not decodable but all values we set are valid utf-8
+ return index, True
+
+
def execute(module):
state = module.params.get('state')
@@ -157,9 +182,8 @@ def lock(module, state):
msg='%s of lock for %s requested but no session supplied' %
(state, key))
- index, existing = consul_api.kv.get(key)
+ index, changed = _has_value_changed(consul_api, key, value)
- changed = not existing or (existing and existing['Value'] != value)
if changed and not module.check_mode:
if state == 'acquire':
changed = consul_api.kv.put(key, value,
@@ -184,14 +208,14 @@ def add_value(module):
key = module.params.get('key')
value = module.params.get('value')
- index, existing = consul_api.kv.get(key)
+ index, changed = _has_value_changed(consul_api, key, value)
- changed = not existing or (existing and existing['Value'] != value)
if changed and not module.check_mode:
changed = consul_api.kv.put(key, value,
cas=module.params.get('cas'),
flags=module.params.get('flags'))
+ stored = None
if module.params.get('retrieve'):
index, stored = consul_api.kv.get(key)
diff --git a/lib/ansible/modules/crypto/openssl_certificate.py b/lib/ansible/modules/crypto/openssl_certificate.py
index 7d62a8c..f2c7515 100644
--- a/lib/ansible/modules/crypto/openssl_certificate.py
+++ b/lib/ansible/modules/crypto/openssl_certificate.py
@@ -423,6 +423,7 @@ class SelfSignedCertificate(Certificate):
self.notAfter = module.params['selfsigned_notAfter']
self.digest = module.params['selfsigned_digest']
self.version = module.params['selfsigned_version']
+ self.serial_number = randint(1000, 99999)
self.csr = crypto_utils.load_certificate_request(self.csr_path)
self.privatekey = crypto_utils.load_privatekey(
self.privatekey_path, self.privatekey_passphrase
@@ -442,7 +443,7 @@ class SelfSignedCertificate(Certificate):
if not self.check(module, perms_required=False) or self.force:
cert = crypto.X509()
- cert.set_serial_number(randint(1000, 99999))
+ cert.set_serial_number(self.serial_number)
if self.notBefore:
cert.set_notBefore(self.notBefore)
else:
@@ -474,18 +475,30 @@ class SelfSignedCertificate(Certificate):
if module.set_fs_attributes_if_different(file_args, False):
self.changed = True
- def dump(self):
+ def dump(self, check_mode=False):
result = {
'changed': self.changed,
'filename': self.path,
'privatekey': self.privatekey_path,
- 'csr': self.csr_path,
- 'notBefore': self.cert.get_notBefore(),
- 'notAfter': self.cert.get_notAfter(),
- 'serial_number': self.cert.get_serial_number(),
+ 'csr': self.csr_path
}
+ if check_mode:
+ now = datetime.datetime.utcnow()
+ ten = now.replace(now.year + 10)
+ result.update({
+ 'notBefore': self.notBefore if self.notBefore else now.strftime("%Y%m%d%H%M%SZ"),
+ 'notAfter': self.notAfter if self.notAfter else ten.strftime("%Y%m%d%H%M%SZ"),
+ 'serial_number': self.serial_number,
+ })
+ else:
+ result.update({
+ 'notBefore': self.cert.get_notBefore(),
+ 'notAfter': self.cert.get_notAfter(),
+ 'serial_number': self.cert.get_serial_number(),
+ })
+
return result
@@ -708,7 +721,7 @@ class AssertOnlyCertificate(Certificate):
return parent_check and assertonly_check
- def dump(self):
+ def dump(self, check_mode=False):
result = {
'changed': self.changed,
@@ -773,7 +786,7 @@ class AcmeCertificate(Certificate):
if module.set_fs_attributes_if_different(file_args, False):
self.changed = True
- def dump(self):
+ def dump(self, check_mode=False):
result = {
'changed': self.changed,
@@ -859,7 +872,7 @@ def main():
if module.params['state'] == 'present':
if module.check_mode:
- result = certificate.dump()
+ result = certificate.dump(check_mode=True)
result['changed'] = module.params['force'] or not certificate.check(module)
module.exit_json(**result)
@@ -870,7 +883,7 @@ def main():
else:
if module.check_mode:
- result = certificate.dump()
+ result = certificate.dump(check_mode=True)
result['changed'] = os.path.exists(module.params['path'])
module.exit_json(**result)
diff --git a/lib/ansible/modules/monitoring/grafana_dashboard.py b/lib/ansible/modules/monitoring/grafana_dashboard.py
index e351204..5766e96 100644
--- a/lib/ansible/modules/monitoring/grafana_dashboard.py
+++ b/lib/ansible/modules/monitoring/grafana_dashboard.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-#
-# Copyright 2017, Thierry Sallé (@tsalle)
+
+# Copyright: (c) 2017, Thierry Sallé (@tsalle)
# 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
@@ -16,80 +16,76 @@ DOCUMENTATION = '''
---
module: grafana_dashboard
author:
- - "Thierry Sallé (@tsalle)"
+ - Thierry Sallé (@tsalle)
version_added: "2.5"
-short_description: Manage grafana dashboards
+short_description: Manage Grafana dashboards
description:
- - Create, update, delete, export grafana dashboards via API
+ - Create, update, delete, export Grafana dashboards via API.
options:
grafana_url:
- required: true
description:
- - Grafana url
+ - The Grafana URL.
+ required: true
grafana_user:
- required: false
- default: admin
description:
- - Grafana API user
- grafana_password:
- required: false
+ - The Grafana API user.
default: admin
+ grafana_password:
description:
- - Grafana API password
+ - The Grafana API password.
+ default: admin
grafana_api_key:
- required: false
description:
- - Grafana API key
- - If set, I(grafana_user) and I(grafana_password) will be ignored
+ - The Grafana API key.
+ - If set, I(grafana_user) and I(grafana_password) will be ignored.
org_id:
- required: false
description:
- - Grafana Organisation ID where the dashboard will be imported / exported
- - Not used when I(grafana_api_key) is set, because the grafana_api_key only belong to one organisation.
+ - The Grafana Organisation ID where the dashboard will be imported / exported.
+ - Not used when I(grafana_api_key) is set, because the grafana_api_key only belong to one organisation..
default: 1
state:
- required: true
- default: present
description:
- State of the dashboard.
- choices: ['present', 'absent', 'export']
+ required: true
+ choices: [ absent, export, present ]
+ default: present
slug:
description:
- slug of the dashboard. It's the friendly url name of the dashboard.
- - When state is present, this parameter can override the slug in the meta section of the json file.
+ - When C(state) is C(present), this parameter can override the slug in the meta section of the json file.
- If you want to import a json dashboard exported directly from the interface (not from the api),
- - you have to specify the slug parameter because there is no meta section in the exported json.
+ you have to specify the slug parameter because there is no meta section in the exported json.
path:
description:
- - path to the json file containing the grafana dashboard to import or export.
+ - The path to the json file containing the Grafana dashboard to import or export.
overwrite:
- default: false
description:
- - override existing dashboard when state is present.
+ - Override existing dashboard when state is present.
+ type: bool
+ default: 'no'
message:
description:
- Set a commit message for the version history.
- - Only used when state is present
+ - Only used when C(state) is C(present).
validate_certs:
- default: true
- type: bool
description:
- - If C(no), SSL certificates will not be validated. This should only be used
- - on personally controlled sites using self-signed certificates.
+ - If C(no), SSL certificates will not be validated.
+ - This should only be used on personally controlled sites using self-signed certificates.
+ type: bool
+ default: 'yes'
'''
EXAMPLES = '''
----
-- name: import grafana dashboard foo
+- name: Import Grafana dashboard foo
grafana_dashboard:
grafana_url: http://grafana.company.com
grafana_api_key: XXXXXXXXXXXX
state: present
- message: "updated by ansible"
- overwrite: true
+ message: Updated by ansible
+ overwrite: yes
path: /path/to/dashboards/foo.json
-- name: export dashboard
+- name: Export dashboard
grafana_dashboard:
grafana_url: http://grafana.company.com
grafana_api_key: XXXXXXXXXXXX
@@ -107,11 +103,12 @@ slug:
sample: foo
'''
-import base64
import json
-import os
+import base64
+
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
+from ansible.module_utils._text import to_bytes
__metaclass__ = type
@@ -166,7 +163,7 @@ def grafana_create_dashboard(module, data):
if 'grafana_api_key' in data and data['grafana_api_key']:
headers['Authorization'] = "Bearer %s" % data['grafana_api_key']
else:
- auth = base64.encodestring('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', '')
+ auth = base64.b64encode(to_bytes('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', ''))
headers['Authorization'] = 'Basic %s' % auth
grafana_switch_organisation(module, data['grafana_url'], data['org_id'], headers)
@@ -224,7 +221,7 @@ def grafana_delete_dashboard(module, data):
if 'grafana_api_key' in data and data['grafana_api_key']:
headers['Authorization'] = "Bearer %s" % data['grafana_api_key']
else:
- auth = base64.encodestring('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', '')
+ auth = base64.b64encode(to_bytes('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', ''))
headers['Authorization'] = 'Basic %s' % auth
grafana_switch_organisation(module, data['grafana_url'], data['org_id'], headers)
@@ -257,7 +254,7 @@ def grafana_export_dashboard(module, data):
if 'grafana_api_key' in data and data['grafana_api_key']:
headers['Authorization'] = "Bearer %s" % data['grafana_api_key']
else:
- auth = base64.encodestring('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', '')
+ auth = base64.b64encode(to_bytes('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', ''))
headers['Authorization'] = 'Basic %s' % auth
grafana_switch_organisation(module, data['grafana_url'], data['org_id'], headers)
diff --git a/lib/ansible/modules/monitoring/grafana_datasource.py b/lib/ansible/modules/monitoring/grafana_datasource.py
index 3e8acde..e1721a6 100644
--- a/lib/ansible/modules/monitoring/grafana_datasource.py
+++ b/lib/ansible/modules/monitoring/grafana_datasource.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-#
-# Copyright 2017, Thierry Sallé (@tsalle)
+
+# Copyright: (c) 2017, Thierry Sallé (@tsalle)
# 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
@@ -16,171 +16,152 @@ DOCUMENTATION = '''
---
module: grafana_datasource
author:
- - "Thierry Sallé (@tsalle)"
+ - Thierry Sallé (@tsalle)
version_added: "2.5"
-short_description: Manage grafana datasources
+short_description: Manage Grafana datasources
description:
- - Create/update/delete grafana datasources via API
+ - Create/update/delete Grafana datasources via API.
options:
grafana_url:
- required: true
description:
- - Grafana url
- name:
+ - The Grafana URL.
required: true
+ name:
description:
- - Name of the datasource
- ds_type:
+ - The name of the datasource.
required: true
- choices: [graphite,prometheus,elasticsearch,influxdb,opentsdb,mysql,postgres]
+ ds_type:
description:
- - Type of the datasource
- url:
+ - The type of the datasource.
required: true
+ choices: [ elasticsearch, graphite, influxdb, mysql, opentsdb, postgres, prometheus ]
+ url:
description:
- - Url of the datasource
+ - The URL of the datasource.
+ required: true
access:
- required: false
- default: proxy
- choices: [proxy,direct]
description:
- - Access mode for this datasource
+ - The access mode for this datasource.
+ choices: [ direct, proxy ]
+ default: proxy
grafana_user:
- required: false
- default: admin
description:
- - Grafana API user
- grafana_password:
- required: false
+ - The Grafana API user.
default: admin
+ grafana_password:
description:
- - Grafana API password
+ - The Grafana API password.
+ default: admin
grafana_api_key:
- required: false
description:
- - Grafana API key
- - If set, grafana_user and grafana_password will be ignored
+ - The Grafana API key.
+ - If set, C(grafana_user) and C(grafana_password) will be ignored.
database:
- required: false
description:
- Name of the database for the datasource.
- - This options is required when the ds_type is influxdb, elasticsearch (index name), mysql or postgres
- user:
+ - This options is required when the C(ds_type) is C(influxdb), C(elasticsearch) (index name), C(mysql) or C(postgres).
required: false
+ user:
description:
- - Datasource login user for influxdb datasources
+ - The datasource login user for influxdb datasources.
password:
- required: false
description:
- - Datasource password
+ - The datasource password
basic_auth_user:
- required: false
description:
- - Datasource basic auth user.
+ - The datasource basic auth user.
- Setting this option with basic_auth_password will enable basic auth.
basic_auth_password:
- required: false
description:
- - Datasource basic auth password, when basic auth is true
+ - The datasource basic auth password, when C(basic auth) is C(yes).
with_credentials:
- required: false
- default: false
description:
- Whether credentials such as cookies or auth headers should be sent with cross-site requests.
+ type: bool
+ default: 'no'
tls_client_cert:
- required: false
description:
- - client tls certificate.
- - If tls_client_cert and tls_client_key are set, this will enable tls auth.
- - begins with ----- BEGIN CERTIFICATE -----
+ - The client TLS certificate.
+ - If C(tls_client_cert) and C(tls_client_key) are set, this will enable TLS authentication.
+ - Starts with ----- BEGIN CERTIFICATE -----
tls_client_key:
- required: false
description:
- - client tls private key
- - befins with ----- BEGIN RSA PRIVATE KEY -----
+ - The client TLS private key
+ - Starts with ----- BEGIN RSA PRIVATE KEY -----
tls_ca_cert:
- required: false
description:
- - tls ca certificate for self signed certificates.
- - only used when tls_client_cert and tls_client_key are set
+ - The TLS CA certificate for self signed certificates.
+ - Only used when C(tls_client_cert) and C(tls_client_key) are set.
is_default:
- required: false
- default: false
description:
- - Make this datasource the default one
+ - Make this datasource the default one.
+ type: bool
+ default: 'no'
org_id:
- required: false
description:
- - Grafana Organisation ID in which the datasource should be created
- - Not used when grafana_api_key is set, because the grafana_api_key only belong to one organisation.
+ - Grafana Organisation ID in which the datasource should be created.
+ - Not used when C(grafana_api_key) is set, because the C(grafana_api_key) only belong to one organisation.
default: 1
state:
- required: false
- default: present
- choices: [present, absent]
description:
- Status of the datasource
+ choices: [ absent, present ]
+ default: present
es_version:
- required: false
- default: 5
- choices: [2, 5, 56]
description:
- - Elasticsearch version (for ds_type = elasticsearch only)
- - Version 56 is for elasticsearch 5.6+ where tou can specify the max_concurrent_shard_requests option.
+ - Elasticsearch version (for C(ds_type = elasticsearch) only)
+ - Version 56 is for elasticsearch 5.6+ where tou can specify the C(max_concurrent_shard_requests) option.
+ choices: [ 2, 5, 56 ]
+ default: 5
max_concurrent_shard_requests:
- required: false
- default: 256
description:
- Starting with elasticsearch 5.6, you can specify the max concurrent shard per requests.
+ default: 256
time_field:
- required: false
- default: timestamp
description:
- - Name of the time field in elasticsearch ds
+ - Name of the time field in elasticsearch ds.
- For example C(@timestamp)
+ default: timestamp
time_interval:
- required: false
description:
- - Minimum group by interval for influxdb or elasticsearch datasources
- - for example '>10s'
+ - Minimum group by interval for C(influxdb) or C(elasticsearch) datasources.
+ - for example C(>10s)
interval:
- required: false
- choices: ['', 'Hourly', 'Daily', 'Weekly', 'Monthly', 'Yearly']
description:
- - for elasticsearch ds_type, this is the index pattern used.
+ - For elasticsearch C(ds_type), this is the index pattern used.
+ choices: ['', 'Hourly', 'Daily', 'Weekly', 'Monthly', 'Yearly']
tsdb_version:
- required: false
- choices: [1, 2, 3]
description:
- - opentsdb version (1 for <= 2.1, 2 for ==2.2, 3 for ==2.3)
+ - The opentsdb version.
+ - Use C(1) for <=2.1, C(2) for ==2.2, C(3) for ==2.3.
+ choices: [ 1, 2, 3 ]
default: 1
tsdb_resolution:
- required: false
- choices: [second, millisecond]
description:
- - opentsdb time resolution
- default: 1
+ - The opentsdb time resolution.
+ choices: [ millisecond, second ]
+ default: second
sslmode:
- choices: ['disable','require','verify-ca','verify-full']
description:
- - SSL mode for postgres datasoure type.
+ - SSL mode for C(postgres) datasoure type.
+ choices: [ disable, require, verify-ca, verify-full ]
validate_certs:
- required: false
- default: True
description:
- - Validate or not grafana certificate
+ - Whether to validate the Grafana certificate.
+ type: bool
+ default: 'yes'
'''
EXAMPLES = '''
---
-- name: create elasticsearch datasource
+- name: Create elasticsearch datasource
grafana_datasource:
name: my_elastic
grafana_url: http://grafana.company.com
type: elasticsearch
url: https://elasticsearch.company.com:9200
database: my-index_*
- basic_auth: true
+ basic_auth: yes
basic_auth_user: grafana
basic_auth_password: xxxxxxxx
json_data: '{"esVersion":5, "timeField": "@timestamp"}'
@@ -243,11 +224,12 @@ after:
"withCredentials": false }
'''
-import base64
import json
-import os
+import base64
+
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
+from ansible.module_utils._text import to_bytes
__metaclass__ = type
@@ -349,7 +331,7 @@ def grafana_create_datasource(module, data):
if 'grafana_api_key' in data and data['grafana_api_key'] is not None:
headers['Authorization'] = "Bearer %s" % data['grafana_api_key']
else:
- auth = base64.encodestring('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', '')
+ auth = base64.b64encode(to_bytes('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', ''))
headers['Authorization'] = 'Basic %s' % auth
grafana_switch_organisation(module, data['grafana_url'], data['org_id'], headers)
@@ -409,7 +391,7 @@ def grafana_delete_datasource(module, data):
if 'grafana_api_key' in data and data['grafana_api_key']:
headers['Authorization'] = "Bearer %s" % data['grafana_api_key']
else:
- auth = base64.encodestring('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', '')
+ auth = base64.b64encode(to_bytes('%s:%s' % (data['grafana_user'], data['grafana_password'])).replace('\n', ''))
headers['Authorization'] = 'Basic %s' % auth
grafana_switch_organisation(module, data['grafana_url'], data['org_id'], headers)
diff --git a/lib/ansible/modules/monitoring/grafana_plugin.py b/lib/ansible/modules/monitoring/grafana_plugin.py
index eb32cf5..1c2a7f6 100644
--- a/lib/ansible/modules/monitoring/grafana_plugin.py
+++ b/lib/ansible/modules/monitoring/grafana_plugin.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-#
-# Copyright 2017, Thierry Sallé (@tsalle)
+
+# Copyright: (c) 2017, Thierry Sallé (@tsalle)
# 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
@@ -16,20 +16,20 @@ DOCUMENTATION = '''
---
module: grafana_plugin
author:
- - "Thierry Sallé (@tsalle)"
+ - Thierry Sallé (@tsalle)
version_added: "2.5"
short_description: Manage Grafana plugins via grafana-cli
description:
- Install and remove Grafana plugins.
options:
name:
- required: true
description:
- Name of the plugin.
+ required: true
version:
description:
- Version of the plugin to install.
- - Default to latest
+ - Default to latest.
grafana_plugins_dir:
description:
- Directory where Grafana plugin will be installed.
@@ -38,19 +38,19 @@ options:
- Grafana repository. If not set, gafana-cli will use the default value C(https://grafana.net/api/plugins).
grafana_plugin_url:
description:
- - Custom Grafana plugin URL
- - Require grafana 4.6.x or later
+ - Custom Grafana plugin URL.
+ - Requires grafana 4.6.x or later.
state:
- default: present
- choices: [present, absent]
description:
- Status of the Grafana plugin.
- If latest is set, the version parameter will be ignored.
+ choices: [ absent, present ]
+ default: present
'''
EXAMPLES = '''
---
-- name: install - update Grafana piechart panel plugin
+- name: Install - update Grafana piechart panel plugin
grafana_plugin:
name: grafana-piechart-panel
version: latest
diff --git a/lib/ansible/modules/monitoring/pagerduty.py b/lib/ansible/modules/monitoring/pagerduty.py
index 7d9f57a..14c15d7 100644
--- a/lib/ansible/modules/monitoring/pagerduty.py
+++ b/lib/ansible/modules/monitoring/pagerduty.py
@@ -164,19 +164,20 @@ EXAMPLES = '''
service: '{{ pd_window.result.maintenance_window.id }}'
'''
-import base64
import datetime
import json
+import base64
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
+from ansible.module_utils._text import to_bytes
def auth_header(user, passwd, token):
if token:
return "Token token=%s" % token
- auth = base64.encodestring('%s:%s' % (user, passwd)).replace('\n', '')
+ auth = base64.b64encode(to_bytes('%s:%s' % (user, passwd)).replace('\n', ''))
return "Basic %s" % auth
diff --git a/lib/ansible/modules/net_tools/haproxy.py b/lib/ansible/modules/net_tools/haproxy.py
index f775f8e..e9ba637 100644
--- a/lib/ansible/modules/net_tools/haproxy.py
+++ b/lib/ansible/modules/net_tools/haproxy.py
@@ -202,6 +202,7 @@ import time
from string import Template
from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_bytes, to_text
DEFAULT_SOCKET_LOCATION = "/var/run/haproxy.sock"
@@ -251,13 +252,16 @@ class HAProxy(object):
"""
self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.client.connect(self.socket)
- self.client.sendall('%s\n' % cmd)
- result = ''
- buf = ''
+ self.client.sendall(to_bytes('%s\n' % cmd))
+
+ result = b''
+ buf = b''
buf = self.client.recv(RECV_SIZE)
while buf:
result += buf
buf = self.client.recv(RECV_SIZE)
+ result = to_text(result, errors='surrogate_or_strict')
+
if capture_output:
self.capture_command_output(cmd, result.strip())
self.client.close()
diff --git a/lib/ansible/modules/net_tools/nios/nios_host_record.py b/lib/ansible/modules/net_tools/nios/nios_host_record.py
index aa9ac0b..e1979b7 100644
--- a/lib/ansible/modules/net_tools/nios/nios_host_record.py
+++ b/lib/ansible/modules/net_tools/nios/nios_host_record.py
@@ -109,7 +109,7 @@ EXAMPLES = '''
nios_host_record:
name: host.ansible.com
ipv4:
- address: 192.168.10.1
+ - address: 192.168.10.1
state: present
provider:
host: "{{ inventory_hostname_short }}"
@@ -121,7 +121,7 @@ EXAMPLES = '''
nios_host_record:
name: host.ansible.com
ipv4:
- address: 192.168.10.1
+ - address: 192.168.10.1
comment: this is a test comment
state: present
provider:
diff --git a/lib/ansible/modules/net_tools/nmcli.py b/lib/ansible/modules/net_tools/nmcli.py
index 18e334d..07a2ed6 100644
--- a/lib/ansible/modules/net_tools/nmcli.py
+++ b/lib/ansible/modules/net_tools/nmcli.py
@@ -34,7 +34,7 @@ options:
- Whether the device should exist or not, taking action if the state is different from what is stated.
autoconnect:
required: False
- default: "yes"
+ default: True
type: bool
description:
- Whether the connection should start on boot.
@@ -1210,7 +1210,7 @@ def main():
# Parsing argument file
module = AnsibleModule(
argument_spec=dict(
- autoconnect=dict(required=False, default=None, type='bool'),
+ autoconnect=dict(required=False, default=True, type='bool'),
state=dict(required=True, choices=['present', 'absent'], type='str'),
conn_name=dict(required=True, type='str'),
master=dict(required=False, default=None, type='str'),
diff --git a/lib/ansible/modules/network/aci/aci_domain_to_vlan_pool.py b/lib/ansible/modules/network/aci/aci_domain_to_vlan_pool.py
index 1064523..cf637d4 100755
--- a/lib/ansible/modules/network/aci/aci_domain_to_vlan_pool.py
+++ b/lib/ansible/modules/network/aci/aci_domain_to_vlan_pool.py
@@ -302,7 +302,7 @@ def main():
elif domain_type == 'vmm':
domain_class = 'vmmDomP'
domain_mo = 'uni/vmmp-{0}/dom-{1}'.format(VM_PROVIDER_MAPPING[vm_provider], domain)
- domain_rn = 'dom-{0}'.format(domain)
+ domain_rn = 'vmmp-{0}/dom-{1}'.format(VM_PROVIDER_MAPPING[vm_provider], domain)
# Ensure that querying all objects works when only domain_type is provided
if domain is None:
diff --git a/lib/ansible/modules/network/citrix/_netscaler.py b/lib/ansible/modules/network/citrix/_netscaler.py
index b8bf36b..e5347ce 100644
--- a/lib/ansible/modules/network/citrix/_netscaler.py
+++ b/lib/ansible/modules/network/citrix/_netscaler.py
@@ -88,14 +88,14 @@ EXAMPLES = '''
action: disable
'''
-import base64
import json
import socket
import traceback
+import base64
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six.moves.urllib.parse import urlencode
-from ansible.module_utils._text import to_native
+from ansible.module_utils._text import to_native, to_bytes
from ansible.module_utils.urls import fetch_url
@@ -115,7 +115,7 @@ class netscaler(object):
if not len(data_json):
data_json = None
- auth = base64.encodestring('%s:%s' % (self._nsc_user, self._nsc_pass)).replace('\n', '').strip()
+ auth = base64.b64encode(to_bytes('%s:%s' % (self._nsc_user, self._nsc_pass)).replace('\n', '').strip())
headers = {
'Authorization': 'Basic %s' % auth,
'Content-Type': 'application/x-www-form-urlencoded',
diff --git a/lib/ansible/modules/network/edgeos/edgeos_config.py b/lib/ansible/modules/network/edgeos/edgeos_config.py
index 6ca1106..4ebe702 100644
--- a/lib/ansible/modules/network/edgeos/edgeos_config.py
+++ b/lib/ansible/modules/network/edgeos/edgeos_config.py
@@ -9,7 +9,7 @@ __metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
- 'supported_by': 'network'}
+ 'supported_by': 'community'}
DOCUMENTATION = """
---
diff --git a/lib/ansible/modules/network/edgeos/edgeos_facts.py b/lib/ansible/modules/network/edgeos/edgeos_facts.py
index d003854..2a5a049 100644
--- a/lib/ansible/modules/network/edgeos/edgeos_facts.py
+++ b/lib/ansible/modules/network/edgeos/edgeos_facts.py
@@ -9,7 +9,7 @@ __metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
- 'supported_by': 'network'}
+ 'supported_by': 'community'}
DOCUMENTATION = """
diff --git a/lib/ansible/modules/network/eos/eos_config.py b/lib/ansible/modules/network/eos/eos_config.py
index 2d1cd13..d4a79d0 100644
--- a/lib/ansible/modules/network/eos/eos_config.py
+++ b/lib/ansible/modules/network/eos/eos_config.py
@@ -126,8 +126,9 @@ options:
- This argument will cause the module to create a full backup of
the current C(running-config) from the remote device before any
changes are made. The backup file is written to the C(backup)
- folder in the playbook root directory. If the directory does not
- exist, it is created.
+ folder in the playbook root directory or role root directory, if
+ playbook is part of an ansible role. If the directory does not exist,
+ it is created.
required: false
default: no
type: bool
diff --git a/lib/ansible/modules/network/eos/eos_vlan.py b/lib/ansible/modules/network/eos/eos_vlan.py
index 4233291..5958b9b 100644
--- a/lib/ansible/modules/network/eos/eos_vlan.py
+++ b/lib/ansible/modules/network/eos/eos_vlan.py
@@ -213,24 +213,22 @@ def map_obj_to_commands(updates, module):
def map_config_to_obj(module):
objs = []
- output = run_commands(module, ['show vlan'])
- lines = output[0].strip().splitlines()[2:]
+ vlans = run_commands(module, ['show vlan conf | json'])
- for l in lines:
- splitted_line = re.split(r'\s{2,}', l.strip())
+ for vlan in vlans[0]['vlans']:
obj = {}
- obj['vlan_id'] = splitted_line[0]
- obj['name'] = splitted_line[1]
- obj['state'] = splitted_line[2]
+ obj['vlan_id'] = vlan
+ obj['name'] = vlans[0]['vlans'][vlan]['name']
+ obj['state'] = vlans[0]['vlans'][vlan]['status']
+ obj['interfaces'] = []
- if obj['state'] == 'suspended':
- obj['state'] = 'suspend'
+ interfaces = vlans[0]['vlans'][vlan]
- obj['interfaces'] = []
- if len(splitted_line) > 3:
+ for interface in interfaces['interfaces']:
+ obj['interfaces'].append(interface)
- for i in splitted_line[3].split(','):
- obj['interfaces'].append(i.strip().replace('Et', 'ethernet'))
+ if obj['state'] == 'suspended':
+ obj['state'] = 'suspend'
objs.append(obj)
diff --git a/lib/ansible/modules/network/ios/ios_config.py b/lib/ansible/modules/network/ios/ios_config.py
index 326ed1a..244baee 100644
--- a/lib/ansible/modules/network/ios/ios_config.py
+++ b/lib/ansible/modules/network/ios/ios_config.py
@@ -131,8 +131,9 @@ options:
- This argument will cause the module to create a full backup of
the current C(running-config) from the remote device before any
changes are made. The backup file is written to the C(backup)
- folder in the playbook root directory. If the directory does not
- exist, it is created.
+ folder in the playbook root directory or role root directory, if
+ playbook is part of an ansible role. If the directory does not exist,
+ it is created.
required: false
default: no
type: bool
diff --git a/lib/ansible/modules/network/ios/ios_interface.py b/lib/ansible/modules/network/ios/ios_interface.py
index 56d6c7d..93e42bc 100644
--- a/lib/ansible/modules/network/ios/ios_interface.py
+++ b/lib/ansible/modules/network/ios/ios_interface.py
@@ -58,15 +58,15 @@ options:
- Supports conditionals, see L(Conditionals in Networking Modules,../network/user_guide/network_working_with_command_output.html)
neighbors:
description:
- - Check the operational state of given interface C(name) for LLDP neighbor.
+ - Check the operational state of given interface C(name) for CDP/LLDP neighbor.
- The following suboptions are available.
suboptions:
host:
description:
- - "LLDP neighbor host for given interface C(name)."
+ - "CDP/LLDP neighbor host for given interface C(name)."
port:
description:
- - "LLDP neighbor port to which given interface C(name) is connected."
+ - "CDP/LLDP neighbor port to which given interface C(name) is connected."
aggregate:
description: List of Interfaces definitions.
delay:
@@ -328,7 +328,8 @@ def map_obj_to_commands(updates):
def check_declarative_intent_params(module, want, result):
failed_conditions = []
- have_neighbors = None
+ have_neighbors_lldp = None
+ have_neighbors_cdp = None
for w in want:
want_state = w.get('state')
want_tx_rate = w.get('tx_rate')
@@ -375,13 +376,15 @@ def check_declarative_intent_params(module, want, result):
if want_neighbors:
have_host = []
have_port = []
- if have_neighbors is None:
- rc, have_neighbors, err = exec_command(module, 'show lldp neighbors detail')
+
+ # Process LLDP neighbors
+ if have_neighbors_lldp is None:
+ rc, have_neighbors_lldp, err = exec_command(module, 'show lldp neighbors detail')
if rc != 0:
module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc)
- if have_neighbors:
- lines = have_neighbors.strip().split('Local Intf: ')
+ if have_neighbors_lldp:
+ lines = have_neighbors_lldp.strip().split('Local Intf: ')
for line in lines:
field = line.split('\n')
if field[0].strip() == w['name']:
@@ -390,6 +393,20 @@ def check_declarative_intent_params(module, want, result):
have_host.append(item.split(':')[1].strip())
if item.startswith('Port Description:'):
have_port.append(item.split(':')[1].strip())
+
+ # Process CDP neighbors
+ if have_neighbors_cdp is None:
+ rc, have_neighbors_cdp, err = exec_command(module, 'show cdp neighbors detail')
+ if rc != 0:
+ module.fail_json(msg=to_text(err, errors='surrogate_then_replace'), command=command, rc=rc)
+
+ if have_neighbors_cdp:
+ neighbors_cdp = re.findall('Device ID: (.*?)\n.*?Interface: (.*?), Port ID .outgoing port.: (.*?)\n', have_neighbors_cdp, re.S)
+ for host, localif, remoteif in neighbors_cdp:
+ if localif == w['name']:
+ have_host.append(host)
+ have_port.append(remoteif)
+
for item in want_neighbors:
host = item.get('host')
port = item.get('port')
diff --git a/lib/ansible/modules/network/ios/ios_l2_interface.py b/lib/ansible/modules/network/ios/ios_l2_interface.py
index 478ac1a..27292bf 100644
--- a/lib/ansible/modules/network/ios/ios_l2_interface.py
+++ b/lib/ansible/modules/network/ios/ios_l2_interface.py
@@ -185,18 +185,21 @@ def remove_switchport_config_commands(name, existing, proposed, module):
commands.append(command)
elif mode == 'trunk':
- tv_check = existing.get('trunk_vlans_list') == proposed.get('trunk_vlans_list')
-
- if not tv_check:
- existing_vlans = existing.get('trunk_vlans_list')
- proposed_vlans = proposed.get('trunk_vlans_list')
- vlans_to_remove = set(proposed_vlans).intersection(existing_vlans)
-
- if vlans_to_remove:
- proposed_allowed_vlans = proposed.get('trunk_allowed_vlans')
- remove_trunk_allowed_vlans = proposed.get('trunk_vlans', proposed_allowed_vlans)
- command = 'switchport trunk allowed vlan remove {0}'.format(remove_trunk_allowed_vlans)
- commands.append(command)
+ # Supported Remove Scenarios for trunk_vlans_list
+ # 1) Existing: 1,2,3 Proposed: 1,2,3 - Remove all
+ # 2) Existing: 1,2,3 Proposed: 1,2 - Remove 1,2 Leave 3
+ # 3) Existing: 1,2,3 Proposed: 2,3 - Remove 2,3 Leave 1
+ # 4) Existing: 1,2,3 Proposed: 4,5,6 - None removed.
+ # 5) Existing: None Proposed: 1,2,3 - None removed.
+ existing_vlans = existing.get('trunk_vlans_list')
+ proposed_vlans = proposed.get('trunk_vlans_list')
+ vlans_to_remove = set(proposed_vlans).intersection(existing_vlans)
+
+ if vlans_to_remove:
+ proposed_allowed_vlans = proposed.get('trunk_allowed_vlans')
+ remove_trunk_allowed_vlans = proposed.get('trunk_vlans', proposed_allowed_vlans)
+ command = 'switchport trunk allowed vlan remove {0}'.format(remove_trunk_allowed_vlans)
+ commands.append(command)
native_check = existing.get('native_vlan') == proposed.get('native_vlan')
if native_check and proposed.get('native_vlan'):
@@ -291,7 +294,7 @@ def vlan_range_to_list(vlans):
result = []
if vlans:
for part in vlans.split(','):
- if part == 'none':
+ if part.lower() == 'none':
break
if '-' in part:
start, stop = (int(i) for i in part.split('-'))
diff --git a/lib/ansible/modules/network/iosxr/iosxr_config.py b/lib/ansible/modules/network/iosxr/iosxr_config.py
index 10a98b1..c3691fc 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_config.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_config.py
@@ -128,8 +128,9 @@ options:
- This argument will cause the module to create a full backup of
the current C(running-config) from the remote device before any
changes are made. The backup file is written to the C(backup)
- folder in the playbook root directory. If the directory does not
- exist, it is created.
+ folder in the playbook root directory or role root directory, if
+ playbook is part of an ansible role. If the directory does not exist,
+ it is created.
required: false
default: no
choices: ['yes', 'no']
diff --git a/lib/ansible/modules/network/junos/junos_banner.py b/lib/ansible/modules/network/junos/junos_banner.py
index dd3f8ef..42ee6cd 100644
--- a/lib/ansible/modules/network/junos/junos_banner.py
+++ b/lib/ansible/modules/network/junos/junos_banner.py
@@ -54,6 +54,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/junos/junos_command.py b/lib/ansible/modules/network/junos/junos_command.py
index cc8ae31..81c2d7f 100644
--- a/lib/ansible/modules/network/junos/junos_command.py
+++ b/lib/ansible/modules/network/junos/junos_command.py
@@ -102,6 +102,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(network_cli) connections and with C(local) connections for legacy playbooks.
"""
EXAMPLES = """
diff --git a/lib/ansible/modules/network/junos/junos_config.py b/lib/ansible/modules/network/junos/junos_config.py
index 10e6d7e..e1cbf5b 100644
--- a/lib/ansible/modules/network/junos/junos_config.py
+++ b/lib/ansible/modules/network/junos/junos_config.py
@@ -105,8 +105,9 @@ options:
- This argument will cause the module to create a full backup of
the current C(running-config) from the remote device before any
changes are made. The backup file is written to the C(backup)
- folder in the playbook root directory. If the directory does not
- exist, it is created.
+ folder in the playbook root directory or role root directory, if
+ playbook is part of an ansible role. If the directory does not exist,
+ it is created.
required: false
default: no
choices: ['yes', 'no']
@@ -145,6 +146,8 @@ notes:
- Loading JSON-formatted configuration I(json) is supported
starting in Junos OS Release 16.1 onwards.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
"""
EXAMPLES = """
diff --git a/lib/ansible/modules/network/junos/junos_facts.py b/lib/ansible/modules/network/junos/junos_facts.py
index 62aab0b..22bf248 100644
--- a/lib/ansible/modules/network/junos/junos_facts.py
+++ b/lib/ansible/modules/network/junos/junos_facts.py
@@ -60,6 +60,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
"""
EXAMPLES = """
diff --git a/lib/ansible/modules/network/junos/junos_interface.py b/lib/ansible/modules/network/junos/junos_interface.py
index b63b8e9..aecf914 100644
--- a/lib/ansible/modules/network/junos/junos_interface.py
+++ b/lib/ansible/modules/network/junos/junos_interface.py
@@ -90,6 +90,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/junos/junos_l2_interface.py b/lib/ansible/modules/network/junos/junos_l2_interface.py
index 53ae768..23de9b1 100644
--- a/lib/ansible/modules/network/junos/junos_l2_interface.py
+++ b/lib/ansible/modules/network/junos/junos_l2_interface.py
@@ -69,6 +69,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/junos/junos_l3_interface.py b/lib/ansible/modules/network/junos/junos_l3_interface.py
index e7f588d..765258b 100644
--- a/lib/ansible/modules/network/junos/junos_l3_interface.py
+++ b/lib/ansible/modules/network/junos/junos_l3_interface.py
@@ -54,6 +54,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/junos/junos_linkagg.py b/lib/ansible/modules/network/junos/junos_linkagg.py
index 2c9774f..85ad848 100644
--- a/lib/ansible/modules/network/junos/junos_linkagg.py
+++ b/lib/ansible/modules/network/junos/junos_linkagg.py
@@ -69,6 +69,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/junos/junos_lldp.py b/lib/ansible/modules/network/junos/junos_lldp.py
index 063800a..989dbe5 100644
--- a/lib/ansible/modules/network/junos/junos_lldp.py
+++ b/lib/ansible/modules/network/junos/junos_lldp.py
@@ -56,6 +56,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/junos/junos_lldp_interface.py b/lib/ansible/modules/network/junos/junos_lldp_interface.py
index 54228b7..a5c90b4 100644
--- a/lib/ansible/modules/network/junos/junos_lldp_interface.py
+++ b/lib/ansible/modules/network/junos/junos_lldp_interface.py
@@ -45,6 +45,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/junos/junos_logging.py b/lib/ansible/modules/network/junos/junos_logging.py
index 825da3e..afc8a42 100644
--- a/lib/ansible/modules/network/junos/junos_logging.py
+++ b/lib/ansible/modules/network/junos/junos_logging.py
@@ -73,6 +73,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/junos/junos_netconf.py b/lib/ansible/modules/network/junos/junos_netconf.py
index 6b085e4..b12a6d1 100644
--- a/lib/ansible/modules/network/junos/junos_netconf.py
+++ b/lib/ansible/modules/network/junos/junos_netconf.py
@@ -48,6 +48,8 @@ options:
choices: ['present', 'absent']
notes:
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(network_cli). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
"""
EXAMPLES = """
diff --git a/lib/ansible/modules/network/junos/junos_package.py b/lib/ansible/modules/network/junos/junos_package.py
index fcaae78..19998a0 100644
--- a/lib/ansible/modules/network/junos/junos_package.py
+++ b/lib/ansible/modules/network/junos/junos_package.py
@@ -86,6 +86,7 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Works with C(local) connections only.
"""
EXAMPLES = """
diff --git a/lib/ansible/modules/network/junos/junos_rpc.py b/lib/ansible/modules/network/junos/junos_rpc.py
index c231153..bc19edf 100644
--- a/lib/ansible/modules/network/junos/junos_rpc.py
+++ b/lib/ansible/modules/network/junos/junos_rpc.py
@@ -58,6 +58,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
"""
EXAMPLES = """
diff --git a/lib/ansible/modules/network/junos/junos_scp.py b/lib/ansible/modules/network/junos/junos_scp.py
index e03f71a..b85d652 100644
--- a/lib/ansible/modules/network/junos/junos_scp.py
+++ b/lib/ansible/modules/network/junos/junos_scp.py
@@ -58,6 +58,7 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vMX JUNOS version 17.3R1.10.
+ - Works with C(local) connections only.
"""
EXAMPLES = """
diff --git a/lib/ansible/modules/network/junos/junos_static_route.py b/lib/ansible/modules/network/junos/junos_static_route.py
index d8a942d..75465d0 100644
--- a/lib/ansible/modules/network/junos/junos_static_route.py
+++ b/lib/ansible/modules/network/junos/junos_static_route.py
@@ -61,6 +61,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/junos/junos_system.py b/lib/ansible/modules/network/junos/junos_system.py
index 80fdba5..3970257 100644
--- a/lib/ansible/modules/network/junos/junos_system.py
+++ b/lib/ansible/modules/network/junos/junos_system.py
@@ -66,6 +66,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/junos/junos_user.py b/lib/ansible/modules/network/junos/junos_user.py
index f936ff8..9486357 100644
--- a/lib/ansible/modules/network/junos/junos_user.py
+++ b/lib/ansible/modules/network/junos/junos_user.py
@@ -96,6 +96,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
"""
EXAMPLES = """
diff --git a/lib/ansible/modules/network/junos/junos_vlan.py b/lib/ansible/modules/network/junos/junos_vlan.py
index 5bf2e14..a9230bf 100644
--- a/lib/ansible/modules/network/junos/junos_vlan.py
+++ b/lib/ansible/modules/network/junos/junos_vlan.py
@@ -55,6 +55,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/junos/junos_vrf.py b/lib/ansible/modules/network/junos/junos_vrf.py
index 3dfaaf4..a5eacc6 100644
--- a/lib/ansible/modules/network/junos/junos_vrf.py
+++ b/lib/ansible/modules/network/junos/junos_vrf.py
@@ -80,6 +80,8 @@ notes:
- This module requires the netconf system service be enabled on
the remote device being managed.
- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
+ - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
+ - This module also works with C(local) connections for legacy playbooks.
extends_documentation_fragment: junos
"""
diff --git a/lib/ansible/modules/network/nso/nso_verify.py b/lib/ansible/modules/network/nso/nso_verify.py
index 74b3f76..cdb9d42 100644
--- a/lib/ansible/modules/network/nso/nso_verify.py
+++ b/lib/ansible/modules/network/nso/nso_verify.py
@@ -107,7 +107,7 @@ class NsoVerify(object):
violations = []
# build list of values from configured data
- value_builder = ValueBuilder(self._client)
+ value_builder = ValueBuilder(self._client, 'verify')
for key, value in self._data.items():
value_builder.build('', key, value)
@@ -137,11 +137,17 @@ class NsoVerify(object):
n_value = normalize_value(
expected_value.value, value, expected_value.path)
if n_value != expected_value.value:
- violations.append({
- 'path': expected_value.path,
- 'expected-value': expected_value.value,
- 'value': n_value
- })
+ # if the value comparision fails, try mapping identityref
+ value_type = value_builder.get_type(expected_value.path)
+ if value_type is not None and 'identityref' in value_type:
+ n_value, t_value = self.get_prefix_name(value)
+
+ if expected_value.value != n_value:
+ violations.append({
+ 'path': expected_value.path,
+ 'expected-value': expected_value.value,
+ 'value': n_value
+ })
else:
raise ModuleFailException(
'value state {0} not supported at {1}'.format(
diff --git a/lib/ansible/modules/network/nxos/_nxos_switchport.py b/lib/ansible/modules/network/nxos/_nxos_switchport.py
index 7b6a2aa..ed8c474 100644
--- a/lib/ansible/modules/network/nxos/_nxos_switchport.py
+++ b/lib/ansible/modules/network/nxos/_nxos_switchport.py
@@ -269,18 +269,23 @@ def remove_switchport_config_commands(interface, existing, proposed, module):
commands.append(command)
elif mode == 'trunk':
- tv_check = existing.get('trunk_vlans_list') == proposed.get('trunk_vlans_list')
-
- if tv_check:
- existing_vlans = existing.get('trunk_vlans_list')
- proposed_vlans = proposed.get('trunk_vlans_list')
- vlans_to_remove = set(proposed_vlans).intersection(existing_vlans)
- if vlans_to_remove:
- proposed_allowed_vlans = proposed.get('trunk_allowed_vlans')
- remove_trunk_allowed_vlans = proposed.get('trunk_vlans', proposed_allowed_vlans)
- command = 'switchport trunk allowed vlan remove {0}'.format(remove_trunk_allowed_vlans)
- commands.append(command)
+ # Supported Remove Scenarios for trunk_vlans_list
+ # 1) Existing: 1,2,3 Proposed: 1,2,3 - Remove all
+ # 2) Existing: 1,2,3 Proposed: 1,2 - Remove 1,2 Leave 3
+ # 3) Existing: 1,2,3 Proposed: 2,3 - Remove 2,3 Leave 1
+ # 4) Existing: 1,2,3 Proposed: 4,5,6 - None removed.
+ # 5) Existing: None Proposed: 1,2,3 - None removed.
+
+ existing_vlans = existing.get('trunk_vlans_list')
+ proposed_vlans = proposed.get('trunk_vlans_list')
+ vlans_to_remove = set(proposed_vlans).intersection(existing_vlans)
+
+ if vlans_to_remove:
+ proposed_allowed_vlans = proposed.get('trunk_allowed_vlans')
+ remove_trunk_allowed_vlans = proposed.get('trunk_vlans', proposed_allowed_vlans)
+ command = 'switchport trunk allowed vlan remove {0}'.format(remove_trunk_allowed_vlans)
+ commands.append(command)
native_check = existing.get('native_vlan') == proposed.get('native_vlan')
if native_check and proposed.get('native_vlan'):
diff --git a/lib/ansible/modules/network/nxos/nxos_aaa_server.py b/lib/ansible/modules/network/nxos/nxos_aaa_server.py
index e95fd42..6d705a4 100644
--- a/lib/ansible/modules/network/nxos/nxos_aaa_server.py
+++ b/lib/ansible/modules/network/nxos/nxos_aaa_server.py
@@ -39,7 +39,6 @@ notes:
stored as encrypted (type 7).
- Changes to the global AAA server key with encrypt_type=0
are not idempotent.
- - If global AAA server key is not found, it's shown as "unknown"
- state=default will set the supplied parameters to their default values.
The parameters that you want to default must also be set to default.
If global_key=default, the global key will be removed.
@@ -51,9 +50,7 @@ options:
choices: ['radius', 'tacacs']
global_key:
description:
- - Global AAA shared secret.
- required: false
- default: null
+ - Global AAA shared secret or keyword 'default'.
encrypt_type:
description:
- The state of encryption applied to the entered global key.
@@ -64,18 +61,17 @@ options:
deadtime:
description:
- Duration for which a non-reachable AAA server is skipped,
- in minutes. Range is 1-1440. Device default is 0.
+ in minutes or keyword 'default.
+ Range is 1-1440. Device default is 0.
required: false
default: null
server_timeout:
description:
- - Global AAA server timeout period, in seconds. Range is 1-60.
- Device default is 5.
- required: false
- default: null
+ - Global AAA server timeout period, in seconds or keyword 'default.
+ Range is 1-60. Device default is 5.
directed_request:
description:
- - Enables direct authentication requests to AAA server.
+ - Enables direct authentication requests to AAA server or keyword 'default'
Device default is disabled.
required: false
default: null
@@ -127,7 +123,14 @@ from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, check_arg
from ansible.module_utils.basic import AnsibleModule
-def execute_show_command(command, module, command_type='cli_show'):
+PARAM_TO_DEFAULT_KEYMAP = {
+ 'server_timeout': '5',
+ 'deadtime': '0',
+ 'directed_request': 'disabled',
+}
+
+
+def execute_show_command(command, module):
command = {
'command': command,
'output': 'text',
@@ -153,8 +156,7 @@ def get_aaa_server_info(server_type, module):
global_key_command = 'show run | sec {0}'.format(server_type)
aaa_regex = r'.*{0}-server\skey\s\d\s+(?P<key>\S+).*'.format(server_type)
- server_body = execute_show_command(
- server_command, module, command_type='cli_show_ascii')[0]
+ server_body = execute_show_command(server_command, module)[0]
split_server = server_body.splitlines()
@@ -165,30 +167,25 @@ def get_aaa_server_info(server_type, module):
elif line.startswith('deadtime'):
aaa_server_info['deadtime'] = line.split(':')[1]
- request_body = execute_show_command(
- request_command, module, command_type='cli_show_ascii')[0]
- aaa_server_info['directed_request'] = request_body.replace('\n', '')
+ request_body = execute_show_command(request_command, module)[0]
- key_body = execute_show_command(
- global_key_command, module, command_type='cli_show_ascii')[0]
+ if bool(request_body):
+ aaa_server_info['directed_request'] = request_body.replace('\n', '')
+ else:
+ aaa_server_info['directed_request'] = 'disabled'
+
+ key_body = execute_show_command(global_key_command, module)[0]
try:
match_global_key = re.match(aaa_regex, key_body, re.DOTALL)
group_key = match_global_key.groupdict()
aaa_server_info['global_key'] = group_key["key"].replace('\"', '')
except (AttributeError, TypeError):
- aaa_server_info['global_key'] = 'unknown'
+ aaa_server_info['global_key'] = None
return aaa_server_info
-def set_aaa_server_global_key(encrypt_type, key, server_type):
- if not encrypt_type:
- encrypt_type = ''
- return '{0}-server key {1} {2}'.format(
- server_type, encrypt_type, key)
-
-
def config_aaa_server(params, server_type):
cmds = []
@@ -226,13 +223,13 @@ def default_aaa_server(existing, params, server_type):
global_key = params.get('global_key')
existing_key = existing.get('global_key')
- if deadtime is not None:
+ if deadtime is not None and existing.get('deadtime') != PARAM_TO_DEFAULT_KEYMAP['deadtime']:
cmds.append('no {0}-server deadtime 1'.format(server_type))
- if server_timeout is not None:
+ if server_timeout is not None and existing.get('server_timeout') != PARAM_TO_DEFAULT_KEYMAP['server_timeout']:
cmds.append('no {0}-server timeout 1'.format(server_type))
- if directed_request is not None:
+ if directed_request is not None and existing.get('directed_request') != PARAM_TO_DEFAULT_KEYMAP['directed_request']:
cmds.append('no {0}-server directed-request'.format(server_type))
if global_key is not None and existing_key is not None:
diff --git a/lib/ansible/modules/network/nxos/nxos_aaa_server_host.py b/lib/ansible/modules/network/nxos/nxos_aaa_server_host.py
index 916ca1e..0c1d6bc 100644
--- a/lib/ansible/modules/network/nxos/nxos_aaa_server_host.py
+++ b/lib/ansible/modules/network/nxos/nxos_aaa_server_host.py
@@ -33,7 +33,7 @@ description:
author: Jason Edelman (@jedelman8)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- - Changes to the AAA server host key (shared secret) are not idempotent.
+ - Changes to the host key (shared secret) are not idempotent for type 0.
- If C(state=absent) removes the whole host configuration.
options:
server_type:
@@ -47,41 +47,29 @@ options:
required: true
key:
description:
- - Shared secret for the specified host.
- required: false
- default: null
+ - Shared secret for the specified host or keyword 'default'.
encrypt_type:
description:
- The state of encryption applied to the entered key.
O for clear text, 7 for encrypted. Type-6 encryption is
not supported.
- required: false
- default: null
choices: ['0', '7']
host_timeout:
description:
- - Timeout period for specified host, in seconds. Range is 1-60.
- required: false
- default: null
+ - Timeout period for specified host, in seconds or keyword 'default.
+ Range is 1-60.
auth_port:
description:
- - Alternate UDP port for RADIUS authentication.
- required: false
- default: null
+ - Alternate UDP port for RADIUS authentication or keyword 'default'.
acct_port:
description:
- - Alternate UDP port for RADIUS accounting.
- required: false
- default: null
+ - Alternate UDP port for RADIUS accounting or keyword 'default'.
tacacs_port:
description:
- - Alternate TCP port TACACS Server.
- required: false
- default: null
+ - Alternate TCP port TACACS Server or keyword 'default'.
state:
description:
- Manage the state of the resource.
- required: false
default: present
choices: ['present','absent']
'''
@@ -160,13 +148,11 @@ from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argume
from ansible.module_utils.basic import AnsibleModule
-def execute_show_command(command, module, command_type='cli_show'):
+def execute_show_command(command, module):
device_info = get_capabilities(module)
network_api = device_info.get('network_api', 'nxapi')
if network_api == 'cliconf':
- if 'show run' not in command:
- command += ' | json'
cmds = [command]
body = run_commands(module, cmds)
elif network_api == 'nxapi':
@@ -186,40 +172,36 @@ def flatten_list(command_lists):
return flat_command_list
-def _match_dict(match_list, key_map):
- no_blanks = []
- match_dict = {}
-
- for match_set in match_list:
- match_set = tuple(v for v in match_set if v)
- no_blanks.append(match_set)
-
- for info in no_blanks:
- words = info[0].strip().split()
- length = len(words)
- alt_key = key_map.get(words[0])
- first = alt_key or words[0]
- last = words[length - 1]
- match_dict[first] = last.replace('\"', '')
-
- return match_dict
-
-
def get_aaa_host_info(module, server_type, address):
aaa_host_info = {}
command = 'show run | inc {0}-server.host.{1}'.format(server_type, address)
- body = execute_show_command(command, module, command_type='cli_show_ascii')
-
- if body[0]:
+ body = execute_show_command(command, module)[0]
+ if body:
try:
- pattern = (r'(acct-port \d+)|(timeout \d+)|(auth-port \d+)|'
- r'(key 7 "\w+")|( port \d+)')
- raw_match = re.findall(pattern, body[0])
- aaa_host_info = _match_dict(raw_match, {'acct-port': 'acct_port',
- 'auth-port': 'auth_port',
- 'port': 'tacacs_port',
- 'timeout': 'host_timeout'})
+ if 'radius' in body:
+ pattern = (r'\S+ host \S+(?:\s+key 7\s+(\S+))?(?:\s+auth-port (\d+))?'
+ r'(?:\s+acct-port (\d+))?(?:\s+authentication)?'
+ r'(?:\s+accounting)?(?:\s+timeout (\d+))?')
+ match = re.search(pattern, body)
+ aaa_host_info['key'] = match.group(1)
+ if aaa_host_info['key']:
+ aaa_host_info['key'] = aaa_host_info['key'].replace('"', '')
+ aaa_host_info['encrypt_type'] = '7'
+ aaa_host_info['auth_port'] = match.group(2)
+ aaa_host_info['acct_port'] = match.group(3)
+ aaa_host_info['host_timeout'] = match.group(4)
+ elif 'tacacs' in body:
+ pattern = (r'\S+ host \S+(?:\s+key 7\s+(\S+))?(?:\s+port (\d+))?'
+ r'(?:\s+timeout (\d+))?')
+ match = re.search(pattern, body)
+ aaa_host_info['key'] = match.group(1)
+ if aaa_host_info['key']:
+ aaa_host_info['key'] = aaa_host_info['key'].replace('"', '')
+ aaa_host_info['encrypt_type'] = '7'
+ aaa_host_info['tacacs_port'] = match.group(2)
+ aaa_host_info['host_timeout'] = match.group(3)
+
aaa_host_info['server_type'] = server_type
aaa_host_info['address'] = address
except TypeError:
@@ -230,35 +212,41 @@ def get_aaa_host_info(module, server_type, address):
return aaa_host_info
-def config_aaa_host(server_type, address, params, clear=False):
+def config_aaa_host(server_type, address, params, existing):
cmds = []
-
- if clear:
- cmds.append('no {0}-server host {1}'.format(server_type, address))
-
cmd_str = '{0}-server host {1}'.format(server_type, address)
+ cmd_no_str = 'no ' + cmd_str
key = params.get('key')
enc_type = params.get('encrypt_type', '')
- host_timeout = params.get('host_timeout')
- auth_port = params.get('auth_port')
- acct_port = params.get('acct_port')
- port = params.get('tacacs_port')
-
- if auth_port:
- cmd_str += ' auth-port {0}'.format(auth_port)
- if acct_port:
- cmd_str += ' acct-port {0}'.format(acct_port)
- if port:
- cmd_str += ' port {0}'.format(port)
- if host_timeout:
- cmd_str += ' timeout {0}'.format(host_timeout)
+
+ defval = False
+ nondef = False
+
if key:
- cmds.append('{0}-server host {1} key {2} {3}'.format(server_type,
- address,
- enc_type, key))
+ if key != 'default':
+ cmds.append(cmd_str + ' key {0} {1}'.format(enc_type, key))
+ else:
+ cmds.append(cmd_no_str + ' key 7 {0}'.format(existing.get('key')))
+
+ locdict = {'auth_port': 'auth-port', 'acct_port': 'acct-port',
+ 'tacacs_port': 'port', 'host_timeout': 'timeout'}
+
+ # platform CLI needs the keywords in the following order
+ for key in ['auth_port', 'acct_port', 'tacacs_port', 'host_timeout']:
+ item = params.get(key)
+ if item:
+ if item != 'default':
+ cmd_str += ' {0} {1}'.format(locdict.get(key), item)
+ nondef = True
+ else:
+ cmd_no_str += ' {0} 1'.format(locdict.get(key))
+ defval = True
+ if defval:
+ cmds.append(cmd_no_str)
+ if nondef or not existing:
+ cmds.append(cmd_str)
- cmds.append(cmd_str)
return cmds
@@ -315,24 +303,19 @@ def main():
end_state = existing
commands = []
+ delta = {}
if state == 'present':
- host_timeout = proposed.get('host_timeout')
- if host_timeout:
- try:
- if int(host_timeout) < 1 or int(host_timeout) > 60:
- raise ValueError
- except ValueError:
- module.fail_json(
- msg='host_timeout must be an integer between 1 and 60')
-
- delta = dict(
- set(proposed.items()).difference(existing.items()))
- if delta:
- union = existing.copy()
- union.update(delta)
- command = config_aaa_host(server_type, address, union)
- if command:
- commands.append(command)
+ if not existing:
+ delta = proposed
+ else:
+ for key, value in proposed.items():
+ if value != existing.get(key):
+ if value != 'default' or existing.get(key):
+ delta[key] = value
+
+ command = config_aaa_host(server_type, address, delta, existing)
+ if command:
+ commands.append(command)
elif state == 'absent':
intersect = dict(
diff --git a/lib/ansible/modules/network/nxos/nxos_acl.py b/lib/ansible/modules/network/nxos/nxos_acl.py
index 1c464d8..9801abc 100644
--- a/lib/ansible/modules/network/nxos/nxos_acl.py
+++ b/lib/ansible/modules/network/nxos/nxos_acl.py
@@ -252,6 +252,7 @@ def get_acl(module, acl_name, seq_number):
for acl in all_acl_body:
if acl.get('acl_name') == acl_name:
acl_body = acl
+ break
try:
acl_entries = acl_body['TABLE_seqno']['ROW_seqno']
@@ -275,7 +276,7 @@ def get_acl(module, acl_name, seq_number):
temp['action'] = 'remark'
else:
temp['action'] = each.get('permitdeny')
- temp['proto'] = each.get('proto', each.get('proto_str', each.get('ip')))
+ temp['proto'] = str(each.get('proto', each.get('proto_str', each.get('ip'))))
temp['src'] = each.get('src_any', each.get('src_ip_prefix'))
temp['src_port_op'] = each.get('src_port_op')
temp['src_port1'] = each.get('src_port1_num')
@@ -507,13 +508,35 @@ def main():
delta_options = {}
if not existing_core.get('remark'):
- delta_core = dict(
+ dcore = dict(
set(proposed_core.items()).difference(
existing_core.items())
)
- delta_options = dict(
- set(proposed_options.items()).difference(
- existing_options.items())
+ if not dcore:
+ # check the diff in the other way just in case
+ dcore = dict(
+ set(existing_core.items()).difference(
+ proposed_core.items())
+ )
+ delta_core = dcore
+ if delta_core:
+ delta_options = proposed_options
+ else:
+ doptions = dict(
+ set(proposed_options.items()).difference(
+ existing_options.items())
+ )
+ # check the diff in the other way just in case
+ if not doptions:
+ doptions = dict(
+ set(existing_options.items()).difference(
+ proposed_options.items())
+ )
+ delta_options = doptions
+ else:
+ delta_core = dict(
+ set(proposed_core.items()).difference(
+ existing_core.items())
)
if state == 'present':
diff --git a/lib/ansible/modules/network/nxos/nxos_bgp_af.py b/lib/ansible/modules/network/nxos/nxos_bgp_af.py
index d055274..5b1d88f 100644
--- a/lib/ansible/modules/network/nxos/nxos_bgp_af.py
+++ b/lib/ansible/modules/network/nxos/nxos_bgp_af.py
@@ -543,14 +543,16 @@ def get_network_command(existing, key, value):
command = '{0} {1}'.format(key, inet[0])
elif len(inet) == 2:
command = '{0} {1} route-map {2}'.format(key, inet[0], inet[1])
- commands.append(command)
+ if command:
+ commands.append(command)
for enet in existing_networks:
if enet not in value:
if len(enet) == 1:
command = 'no {0} {1}'.format(key, enet[0])
elif len(enet) == 2:
command = 'no {0} {1} route-map {2}'.format(key, enet[0], enet[1])
- commands.append(command)
+ if command:
+ commands.append(command)
return commands
@@ -568,7 +570,8 @@ def get_inject_map_command(existing, key, value):
command = ('inject-map {0} exist-map {1} '
'copy-attributes'.format(maps[0],
maps[1]))
- commands.append(command)
+ if command:
+ commands.append(command)
for emaps in existing_maps:
if emaps not in value:
if len(emaps) == 2:
@@ -578,7 +581,8 @@ def get_inject_map_command(existing, key, value):
command = ('no inject-map {0} exist-map {1} '
'copy-attributes'.format(emaps[0],
emaps[1]))
- commands.append(command)
+ if command:
+ commands.append(command)
return commands
diff --git a/lib/ansible/modules/network/nxos/nxos_config.py b/lib/ansible/modules/network/nxos/nxos_config.py
index e598877..69b2ed2 100644
--- a/lib/ansible/modules/network/nxos/nxos_config.py
+++ b/lib/ansible/modules/network/nxos/nxos_config.py
@@ -134,8 +134,9 @@ options:
- This argument will cause the module to create a full backup of
the current C(running-config) from the remote device before any
changes are made. The backup file is written to the C(backup)
- folder in the playbook root directory. If the directory does not
- exist, it is created.
+ folder in the playbook root directory or role root directory, if
+ playbook is part of an ansible role. If the directory does not exist,
+ it is created.
required: false
default: false
type: bool
diff --git a/lib/ansible/modules/network/nxos/nxos_hsrp.py b/lib/ansible/modules/network/nxos/nxos_hsrp.py
index 0faba42..84f6b46 100644
--- a/lib/ansible/modules/network/nxos/nxos_hsrp.py
+++ b/lib/ansible/modules/network/nxos/nxos_hsrp.py
@@ -53,38 +53,32 @@ options:
version:
description:
- HSRP version.
- required: false
- default: 2
+ default: 1
choices: ['1','2']
priority:
description:
- - HSRP priority.
- required: false
- default: null
+ - HSRP priority or keyword 'default'.
preempt:
description:
- Enable/Disable preempt.
choices: ['enabled', 'disabled']
vip:
description:
- - HSRP virtual IP address.
- required: false
- default: null
+ - HSRP virtual IP address or keyword 'default'
auth_string:
description:
- - Authentication string.
- required: false
- default: null
+ - Authentication string. If this needs to be hidden(for md5 type), the string
+ should be 7 followed by the key string. Otherwise, it can be 0 followed by
+ key string or just key string (for backward compatibility). For text type,
+ this should be just be a key string. if this is 'default', authentication
+ is removed.
auth_type:
description:
- Authentication type.
- required: false
- default: null
choices: ['text','md5']
state:
description:
- Specify desired state of the resource.
- required: false
choices: ['present','absent']
default: 'present'
'''
@@ -100,6 +94,7 @@ EXAMPLES = '''
host: 68.170.147.165
- name: Ensure HSRP is configured with following params on a SVI
+ with clear text authentication
nxos_hsrp:
group: 10
vip: 10.1.1.1
@@ -110,6 +105,30 @@ EXAMPLES = '''
auth_type: text
auth_string: CISCO
+- name: Ensure HSRP is configured with md5 authentication and clear
+ authentication string
+ nxos_hsrp:
+ group: 10
+ vip: 10.1.1.1
+ priority: 150
+ interface: vlan10
+ preempt: enabled
+ host: 68.170.147.165
+ auth_type: md5
+ auth_string: "0 1234"
+
+- name: Ensure HSRP is configured with md5 authentication and hidden
+ authentication string
+ nxos_hsrp:
+ group: 10
+ vip: 10.1.1.1
+ priority: 150
+ interface: vlan10
+ preempt: enabled
+ host: 68.170.147.165
+ auth_type: md5
+ auth_string: "7 1234"
+
- name: Remove HSRP config for given interface, group, and VIP
nxos_hsrp:
group: 10
@@ -132,6 +151,14 @@ from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argume
from ansible.module_utils.basic import AnsibleModule
+PARAM_TO_DEFAULT_KEYMAP = {
+ 'vip': None,
+ 'priority': '100',
+ 'auth_type': 'text',
+ 'auth_string': 'cisco',
+}
+
+
def execute_show_command(command, module):
device_info = get_capabilities(module)
network_api = device_info.get('network_api', 'nxapi')
@@ -196,29 +223,8 @@ def get_interface_mode(interface, intf_type, module):
return mode
-def get_hsrp_groups_on_interfaces(device, module):
- command = 'show hsrp all'
- hsrp = {}
-
- try:
- body = execute_show_command(command, module)[0]
- get_data = body['TABLE_grp_detail']['ROW_grp_detail']
- except (IndexError, KeyError, AttributeError):
- return {}
-
- for entry in get_data:
- interface = str(entry['sh_if_index'].lower())
- value = hsrp.get(interface, 'new')
- if value == 'new':
- hsrp[interface] = []
- group = str(entry['sh_group_num'])
- hsrp[interface].append(group)
-
- return hsrp
-
-
def get_hsrp_group(group, interface, module):
- command = 'show hsrp group {0}'.format(group)
+ command = 'show hsrp group {0} all'.format(group)
hsrp = {}
hsrp_key = {
@@ -229,6 +235,7 @@ def get_hsrp_group(group, interface, module):
'sh_preempt': 'preempt',
'sh_vip': 'vip',
'sh_authentication_type': 'auth_type',
+ 'sh_keystring_attr': 'auth_enc',
'sh_authentication_data': 'auth_string'
}
@@ -251,6 +258,12 @@ def get_hsrp_group(group, interface, module):
elif parsed_hsrp['version'] == 'v2':
parsed_hsrp['version'] = '2'
+ if parsed_hsrp['auth_type'] == 'md5':
+ if parsed_hsrp['auth_enc'] == 'hidden':
+ parsed_hsrp['auth_enc'] = '7'
+ else:
+ parsed_hsrp['auth_enc'] = '0'
+
if parsed_hsrp['interface'] == interface:
return parsed_hsrp
@@ -262,24 +275,45 @@ def get_commands_remove_hsrp(group, interface):
return commands
-def get_commands_config_hsrp(delta, interface, args):
+def get_commands_config_hsrp(delta, interface, args, existing):
commands = []
config_args = {
'group': 'hsrp {group}',
- 'priority': 'priority {priority}',
+ 'priority': '{priority}',
'preempt': '{preempt}',
- 'vip': 'ip {vip}'
+ 'vip': '{vip}'
}
preempt = delta.get('preempt', None)
group = delta.get('group', None)
+ vip = delta.get('vip', None)
+ priority = delta.get('priority', None)
+
if preempt:
if preempt == 'enabled':
delta['preempt'] = 'preempt'
elif preempt == 'disabled':
delta['preempt'] = 'no preempt'
+ if priority:
+ if priority == 'default':
+ if existing and existing.get('priority') != PARAM_TO_DEFAULT_KEYMAP.get('priority'):
+ delta['priority'] = 'no priority'
+ else:
+ del(delta['priority'])
+ else:
+ delta['priority'] = 'priority {0}'.format(delta['priority'])
+
+ if vip:
+ if vip == 'default':
+ if existing and existing.get('vip') != PARAM_TO_DEFAULT_KEYMAP.get('vip'):
+ delta['vip'] = 'no ip'
+ else:
+ del(delta['vip'])
+ else:
+ delta['vip'] = 'ip {0}'.format(delta['vip'])
+
for key in delta:
command = config_args.get(key, 'DNE').format(**delta)
if command and command != 'DNE':
@@ -291,17 +325,22 @@ def get_commands_config_hsrp(delta, interface, args):
auth_type = delta.get('auth_type', None)
auth_string = delta.get('auth_string', None)
+ auth_enc = delta.get('auth_enc', None)
if auth_type or auth_string:
if not auth_type:
auth_type = args['auth_type']
elif not auth_string:
auth_string = args['auth_string']
- if auth_type == 'md5':
- command = 'authentication md5 key-string {0}'.format(auth_string)
- commands.append(command)
- elif auth_type == 'text':
- command = 'authentication text {0}'.format(auth_string)
- commands.append(command)
+ if auth_string != 'default':
+ if auth_type == 'md5':
+ command = 'authentication md5 key-string {0} {1}'.format(auth_enc, auth_string)
+ commands.append(command)
+ elif auth_type == 'text':
+ command = 'authentication text {0}'.format(auth_string)
+ commands.append(command)
+ else:
+ if existing and existing.get('auth_string') != PARAM_TO_DEFAULT_KEYMAP.get('auth_string'):
+ commands.append('no authentication')
if commands and not group:
commands.insert(0, 'hsrp {0}'.format(args['group']))
@@ -346,35 +385,11 @@ def validate_config(body, vip, module):
vip=vip)
-def validate_params(param, module):
- value = module.params[param]
- version = module.params['version']
-
- if param == 'group':
- try:
- if (int(value) < 0 or int(value) > 255) and version == '1':
- raise ValueError
- elif int(value) < 0 or int(value) > 4095:
- raise ValueError
- except ValueError:
- module.fail_json(msg="Warning! 'group' must be an integer between"
- " 0 and 255 when version 1 and up to 4095 "
- "when version 2.", group=value,
- version=version)
- elif param == 'priority':
- try:
- if (int(value) < 0 or int(value) > 255):
- raise ValueError
- except ValueError:
- module.fail_json(msg="Warning! 'priority' must be an integer "
- "between 0 and 255", priority=value)
-
-
def main():
argument_spec = dict(
group=dict(required=True, type='str'),
interface=dict(required=True),
- version=dict(choices=['1', '2'], default='2', required=False),
+ version=dict(choices=['1', '2'], default='1', required=False),
priority=dict(type='str', required=False),
preempt=dict(type='str', choices=['disabled', 'enabled'], required=False),
vip=dict(type='str', required=False),
@@ -398,18 +413,24 @@ def main():
preempt = module.params['preempt']
vip = module.params['vip']
auth_type = module.params['auth_type']
- auth_string = module.params['auth_string']
+ auth_full_string = module.params['auth_string']
+ auth_enc = '0'
+ auth_string = None
+ if auth_full_string:
+ kstr = auth_full_string.split()
+ if len(kstr) == 2:
+ auth_enc = kstr[0]
+ auth_string = kstr[1]
+ elif len(kstr) == 1:
+ auth_string = kstr[0]
+ else:
+ module.fail_json(msg='Inavlid auth_string')
+ if auth_enc != '0' and auth_enc != '7':
+ module.fail_json(msg='Inavlid auth_string, only 0 or 7 allowed')
device_info = get_capabilities(module)
network_api = device_info.get('network_api', 'nxapi')
- if state == 'present' and not vip:
- module.fail_json(msg='the "vip" param is required when state=present')
-
- for param in ['group', 'priority']:
- if module.params[param] is not None:
- validate_params(param, module)
-
intf_type = get_interface_type(interface)
if (intf_type != 'ethernet' and network_api == 'cliconf'):
if is_default(interface, module) == 'DNE':
@@ -431,7 +452,7 @@ def main():
args = dict(group=group, version=version, priority=priority,
preempt=preempt, vip=vip, auth_type=auth_type,
- auth_string=auth_string)
+ auth_string=auth_string, auth_enc=auth_enc)
proposed = dict((k, v) for k, v in args.items() if v is not None)
@@ -445,7 +466,7 @@ def main():
elif not proposed.get('auth_type', None) and existing:
if (proposed['version'] == '1' and
- existing['auth_type'] == 'md5'):
+ existing['auth_type'] == 'md5') and state == 'present':
module.fail_json(msg="Existing auth_type is md5. It's recommended "
"to use HSRP v2 when using md5")
@@ -454,7 +475,7 @@ def main():
delta = dict(
set(proposed.items()).difference(existing.items()))
if delta:
- command = get_commands_config_hsrp(delta, interface, args)
+ command = get_commands_config_hsrp(delta, interface, args, existing)
commands.extend(command)
elif state == 'absent':
diff --git a/lib/ansible/modules/network/nxos/nxos_igmp.py b/lib/ansible/modules/network/nxos/nxos_igmp.py
index a1a023c..8694b17 100644
--- a/lib/ansible/modules/network/nxos/nxos_igmp.py
+++ b/lib/ansible/modules/network/nxos/nxos_igmp.py
@@ -139,10 +139,12 @@ def main():
commands.append('no ip igmp enforce-router-alert')
elif state == 'present':
- if desired['flush_routes'] and not current['flush_routes']:
- commands.append('ip igmp flush-routes')
- if desired['enforce_rtr_alert'] and not current['enforce_rtr_alert']:
- commands.append('ip igmp enforce-router-alert')
+ ldict = {'flush_routes': 'flush-routes', 'enforce_rtr_alert': 'enforce-router-alert'}
+ for arg in ['flush_routes', 'enforce_rtr_alert']:
+ if desired[arg] and not current[arg]:
+ commands.append('ip igmp {0}'.format(ldict.get(arg)))
+ elif current[arg] and not desired[arg]:
+ commands.append('no ip igmp {0}'.format(ldict.get(arg)))
result = {'changed': False, 'updates': commands, 'warnings': warnings}
@@ -152,7 +154,8 @@ def main():
result['changed'] = True
if module.params['restart']:
- run_commands(module, 'restart igmp')
+ cmd = {'command': 'restart igmp', 'output': 'text'}
+ run_commands(module, cmd)
module.exit_json(**result)
diff --git a/lib/ansible/modules/network/nxos/nxos_igmp_snooping.py b/lib/ansible/modules/network/nxos/nxos_igmp_snooping.py
index 6d84d40..93721e6 100644
--- a/lib/ansible/modules/network/nxos/nxos_igmp_snooping.py
+++ b/lib/ansible/modules/network/nxos/nxos_igmp_snooping.py
@@ -40,37 +40,27 @@ options:
snooping:
description:
- Enables/disables IGMP snooping on the switch.
- required: false
- default: null
- choices: ['true', 'false']
+ type: bool
group_timeout:
description:
- Group membership timeout value for all VLANs on the device.
Accepted values are integer in range 1-10080, I(never) and
I(default).
- required: false
- default: null
link_local_grp_supp:
description:
- Global link-local groups suppression.
- required: false
- default: null
- choices: ['true', 'false']
+ type: bool
report_supp:
description:
- Global IGMPv1/IGMPv2 Report Suppression.
- required: false
- default: null
+ type: bool
v3_report_supp:
description:
- Global IGMPv3 Report Suppression and Proxy Reporting.
- required: false
- default: null
- choices: ['true', 'false']
+ type: bool
state:
description:
- Manage the state of the resource.
- required: false
default: present
choices: ['present','default']
'''
@@ -138,17 +128,6 @@ def get_group_timeout(config):
return value
-def get_snooping(config):
- REGEX = re.compile(r'{0}$'.format('no ip igmp snooping'), re.M)
- value = False
- try:
- if REGEX.search(config):
- value = False
- except TypeError:
- value = True
- return value
-
-
def get_igmp_snooping(module):
command = 'show ip igmp snooping'
existing = {}
@@ -207,6 +186,9 @@ def config_igmp_snooping(delta, existing, default=False):
if default and key == 'group_timeout':
if existing.get(key):
command = 'no ' + CMDS.get(key).format(existing.get(key))
+ elif value == 'default' and key == 'group_timeout':
+ if existing.get(key):
+ command = 'no ' + CMDS.get(key).format(existing.get(key))
else:
command = CMDS.get(key).format(value)
else:
diff --git a/lib/ansible/modules/network/nxos/nxos_l2_interface.py b/lib/ansible/modules/network/nxos/nxos_l2_interface.py
index 72e3ab2..d7a9add 100644
--- a/lib/ansible/modules/network/nxos/nxos_l2_interface.py
+++ b/lib/ansible/modules/network/nxos/nxos_l2_interface.py
@@ -252,18 +252,15 @@ def remove_switchport_config_commands(name, existing, proposed, module):
commands.append(command)
elif mode == 'trunk':
- tv_check = existing.get('trunk_vlans_list') == proposed.get('trunk_vlans_list')
-
- if tv_check:
- existing_vlans = existing.get('trunk_vlans_list')
- proposed_vlans = proposed.get('trunk_vlans_list')
- vlans_to_remove = set(proposed_vlans).intersection(existing_vlans)
-
- if vlans_to_remove:
- proposed_allowed_vlans = proposed.get('trunk_allowed_vlans')
- remove_trunk_allowed_vlans = proposed.get('trunk_vlans', proposed_allowed_vlans)
- command = 'switchport trunk allowed vlan remove {0}'.format(remove_trunk_allowed_vlans)
- commands.append(command)
+ existing_vlans = existing.get('trunk_vlans_list')
+ proposed_vlans = proposed.get('trunk_vlans_list')
+ vlans_to_remove = set(proposed_vlans).intersection(existing_vlans)
+
+ if vlans_to_remove:
+ proposed_allowed_vlans = proposed.get('trunk_allowed_vlans')
+ remove_trunk_allowed_vlans = proposed.get('trunk_vlans', proposed_allowed_vlans)
+ command = 'switchport trunk allowed vlan remove {0}'.format(remove_trunk_allowed_vlans)
+ commands.append(command)
native_check = existing.get('native_vlan') == proposed.get('native_vlan')
if native_check and proposed.get('native_vlan'):
diff --git a/lib/ansible/modules/network/nxos/nxos_ntp_auth.py b/lib/ansible/modules/network/nxos/nxos_ntp_auth.py
index 23b55ee..49172de 100644
--- a/lib/ansible/modules/network/nxos/nxos_ntp_auth.py
+++ b/lib/ansible/modules/network/nxos/nxos_ntp_auth.py
@@ -34,20 +34,15 @@ author:
- Jason Edelman (@jedelman8)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- - If C(state=absent), the module will attempt to remove the given key configuration.
- If a matching key configuration isn't found on the device, the module will fail.
+ - If C(state=absent), the module will remove the given key configuration if it exists.
- If C(state=absent) and C(authentication=on), authentication will be turned off.
- - If C(state=absent) and C(authentication=off), authentication will be turned on.
options:
key_id:
description:
- Authentication key identifier (numeric).
- required: true
md5string:
description:
- MD5 String.
- required: true
- default: null
auth_type:
description:
- Whether the given md5string is in cleartext or
@@ -162,17 +157,19 @@ def get_ntp_auth_key(key_id, module):
authentication_key = {}
command = 'show run | inc ntp.authentication-key.{0}'.format(key_id)
auth_regex = (r".*ntp\sauthentication-key\s(?P<key_id>\d+)\s"
- r"md5\s(?P<md5string>\S+).*")
+ r"md5\s(?P<md5string>\S+)\s(?P<atype>\S+).*")
body = execute_show_command(command, module)[0]
try:
match_authentication = re.match(auth_regex, body, re.DOTALL)
group_authentication = match_authentication.groupdict()
- key_id = group_authentication["key_id"]
- md5string = group_authentication['md5string']
- authentication_key['key_id'] = key_id
- authentication_key['md5string'] = md5string
+ authentication_key['key_id'] = group_authentication['key_id']
+ authentication_key['md5string'] = group_authentication['md5string']
+ if group_authentication['atype'] == '7':
+ authentication_key['auth_type'] = 'encrypt'
+ else:
+ authentication_key['auth_type'] = 'text'
except (AttributeError, TypeError):
authentication_key = {}
@@ -206,10 +203,11 @@ def auth_type_to_num(auth_type):
def set_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
ntp_auth_cmds = []
- auth_type_num = auth_type_to_num(auth_type)
- ntp_auth_cmds.append(
- 'ntp authentication-key {0} md5 {1} {2}'.format(
- key_id, md5string, auth_type_num))
+ if key_id and md5string:
+ auth_type_num = auth_type_to_num(auth_type)
+ ntp_auth_cmds.append(
+ 'ntp authentication-key {0} md5 {1} {2}'.format(
+ key_id, md5string, auth_type_num))
if trusted_key == 'true':
ntp_auth_cmds.append(
@@ -230,25 +228,22 @@ def set_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
def remove_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
auth_remove_cmds = []
- auth_type_num = auth_type_to_num(auth_type)
- auth_remove_cmds.append(
- 'no ntp authentication-key {0} md5 {1} {2}'.format(
- key_id, md5string, auth_type_num))
+ if key_id:
+ auth_type_num = auth_type_to_num(auth_type)
+ auth_remove_cmds.append(
+ 'no ntp authentication-key {0} md5 {1} {2}'.format(
+ key_id, md5string, auth_type_num))
- if authentication == 'on':
+ if authentication:
auth_remove_cmds.append(
'no ntp authenticate')
- elif authentication == 'off':
- auth_remove_cmds.append(
- 'ntp authenticate')
-
return auth_remove_cmds
def main():
argument_spec = dict(
- key_id=dict(required=True, type='str'),
- md5string=dict(required=True, type='str'),
+ key_id=dict(type='str'),
+ md5string=dict(type='str'),
auth_type=dict(choices=['text', 'encrypt'], default='text'),
trusted_key=dict(choices=['true', 'false'], default='false'),
authentication=dict(choices=['on', 'off']),
@@ -270,6 +265,10 @@ def main():
authentication = module.params['authentication']
state = module.params['state']
+ if key_id:
+ if not trusted_key and not md5string:
+ module.fail_json(msg='trusted_key or md5string MUST be specified')
+
args = dict(key_id=key_id, md5string=md5string,
auth_type=auth_type, trusted_key=trusted_key,
authentication=authentication)
@@ -286,18 +285,20 @@ def main():
if state == 'present':
if delta:
command = set_ntp_auth_key(
- key_id, md5string, auth_type, trusted_key, delta.get('authentication'))
+ key_id, md5string, delta.get('auth_type'),
+ delta.get('trusted_key'), delta.get('authentication'))
if command:
commands.append(command)
elif state == 'absent':
- if existing:
- auth_toggle = None
- if authentication == existing.get('authentication'):
- auth_toggle = authentication
- command = remove_ntp_auth_key(
- key_id, md5string, auth_type, trusted_key, auth_toggle)
- if command:
- commands.append(command)
+ auth_toggle = None
+ if existing.get('authentication') == 'on':
+ auth_toggle = True
+ if not existing.get('key_id'):
+ key_id = None
+ command = remove_ntp_auth_key(
+ key_id, md5string, auth_type, trusted_key, auth_toggle)
+ if command:
+ commands.append(command)
cmds = flatten_list(commands)
if cmds:
diff --git a/lib/ansible/modules/network/nxos/nxos_ntp_options.py b/lib/ansible/modules/network/nxos/nxos_ntp_options.py
index afa535f..3ffe093 100644
--- a/lib/ansible/modules/network/nxos/nxos_ntp_options.py
+++ b/lib/ansible/modules/network/nxos/nxos_ntp_options.py
@@ -34,12 +34,8 @@ author:
- Jason Edelman (@jedelman8)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- - At least one of C(master) or C(logging) params must be supplied.
- - When C(state=absent), boolean parameters are flipped,
- e.g. C(master=true) will disable the authoritative server.
- - When C(state=absent) and C(master=true), the stratum will be removed as well.
- - When C(state=absent) and C(master=false), the stratum will be configured
- to its default value, 8.
+ - When C(state=absent), master and logging will be set to False and
+ stratum will be removed as well
options:
master:
description:
@@ -82,7 +78,7 @@ updates:
description: command sent to the device
returned: always
type: list
- sample: ["no ntp logging", "ntp master 11"]
+ sample: ["no ntp logging", "ntp master 12"]
'''
import re
@@ -92,20 +88,20 @@ from ansible.module_utils.basic import AnsibleModule
def get_current(module):
- cmd = ('show running-config', 'show ntp logging')
+ cmd = ('show running-config | inc ntp')
- output = run_commands(module, ({'command': cmd[0], 'output': 'text'},
- {'command': cmd[1], 'output': 'text'}))
+ master = False
+ logging = False
+ stratum = None
- match = re.search(r"^ntp master(?: (\d+))", output[0], re.M)
- if match:
- master = True
- stratum = match.group(1)
- else:
- master = False
- stratum = None
+ output = run_commands(module, ({'command': cmd, 'output': 'text'}))[0]
- logging = 'enabled' in output[1].lower()
+ if output:
+ match = re.search(r"^ntp master(?: (\d+))", output, re.M)
+ if match:
+ master = True
+ stratum = match.group(1)
+ logging = 'ntp logging' in output.lower()
return {'master': master, 'stratum': stratum, 'logging': logging}
@@ -113,7 +109,7 @@ def get_current(module):
def main():
argument_spec = dict(
master=dict(required=False, type='bool'),
- stratum=dict(required=False, type='str', default='8'),
+ stratum=dict(required=False, type='str'),
logging=dict(required=False, type='bool'),
state=dict(choices=['absent', 'present'], default='present'),
)
@@ -131,15 +127,10 @@ def main():
logging = module.params['logging']
state = module.params['state']
- if stratum:
- try:
- stratum_int = int(stratum)
- if stratum_int < 1 or stratum_int > 15:
- raise ValueError
- except ValueError:
- module.fail_json(msg='stratum must be an integer between 1 and 15')
+ if stratum and master is False:
+ if stratum != 8:
+ module.fail_json(msg='master MUST be True when stratum is changed')
- desired = {'master': master, 'stratum': stratum, 'logging': logging}
current = get_current(module)
result = {'changed': False}
@@ -153,19 +144,17 @@ def main():
commands.append('no ntp logging')
elif state == 'present':
- if desired['master'] and desired['master'] != current['master']:
- if desired['stratum']:
- commands.append('ntp master %s' % stratum)
- else:
- commands.append('ntp master')
- elif desired['stratum'] and desired['stratum'] != current['stratum']:
+ if master and not current['master']:
+ commands.append('ntp master')
+ elif master is False and current['master']:
+ commands.append('no ntp master')
+ if stratum and stratum != current['stratum']:
commands.append('ntp master %s' % stratum)
- if desired['logging'] and desired['logging'] != current['logging']:
- if desired['logging']:
- commands.append('ntp logging')
- else:
- commands.append('no ntp logging')
+ if logging and not current['logging']:
+ commands.append('ntp logging')
+ elif logging is False and current['logging']:
+ commands.append('no ntp logging')
result['commands'] = commands
result['updates'] = commands
diff --git a/lib/ansible/modules/network/nxos/nxos_snapshot.py b/lib/ansible/modules/network/nxos/nxos_snapshot.py
index e8f07af..93a172a 100644
--- a/lib/ansible/modules/network/nxos/nxos_snapshot.py
+++ b/lib/ansible/modules/network/nxos/nxos_snapshot.py
@@ -69,7 +69,7 @@ options:
default: null
comparison_results_file:
description:
- - Name of the file where snapshots comparison will be store.
+ - Name of the file where snapshots comparison will be stored when C(action=compare).
required: false
default: null
compare_option:
@@ -354,7 +354,13 @@ def main():
argument_spec.update(nxos_argument_spec)
+ required_if = [("action", "compare", ["snapshot1", "snapshot2", "comparison_results_file"]),
+ ("action", "create", ["snapshot_name", "description"]),
+ ("action", "add", ["section", "show_command", "row_id", "element_key1"]),
+ ("action", "delete", ["snapshot_name"])]
+
module = AnsibleModule(argument_spec=argument_spec,
+ required_if=required_if,
supports_check_mode=True)
warnings = list()
@@ -363,33 +369,10 @@ def main():
action = module.params['action']
comparison_results_file = module.params['comparison_results_file']
- CREATE_PARAMS = ['snapshot_name', 'description']
- ADD_PARAMS = ['section', 'show_command', 'row_id', 'element_key1']
- COMPARE_PARAMS = ['snapshot1', 'snapshot2', 'comparison_results_file']
-
if not os.path.isdir(module.params['path']):
module.fail_json(msg='{0} is not a valid directory name.'.format(
module.params['path']))
- if action == 'create':
- for param in CREATE_PARAMS:
- if not module.params[param]:
- module.fail_json(msg='snapshot_name and description are '
- 'required when action=create')
- elif action == 'add':
- for param in ADD_PARAMS:
- if not module.params[param]:
- module.fail_json(msg='section, show_command, row_id '
- 'and element_key1 are required '
- 'when action=add')
- elif action == 'compare':
- for param in COMPARE_PARAMS:
- if not module.params[param]:
- module.fail_json(msg='snapshot1 and snapshot2 are required '
- 'when action=create')
- elif action == 'delete' and not module.params['snapshot_name']:
- module.fail_json(msg='snapshot_name is required when action=delete')
-
existing_snapshots = invoke('get_existing', module)
action_results = invoke('action_%s' % action, module, existing_snapshots)
diff --git a/lib/ansible/modules/network/nxos/nxos_static_route.py b/lib/ansible/modules/network/nxos/nxos_static_route.py
index 8a563db..8460996 100644
--- a/lib/ansible/modules/network/nxos/nxos_static_route.py
+++ b/lib/ansible/modules/network/nxos/nxos_static_route.py
@@ -49,23 +49,16 @@ options:
vrf:
description:
- VRF for static route.
- required: false
default: default
tag:
description:
- - Route tag value (numeric).
- required: false
- default: null
+ - Route tag value (numeric) or keyword 'default'.
route_name:
description:
- - Name of the route. Used with the name parameter on the CLI.
- required: false
- default: null
+ - Name of the route or keyword 'default'. Used with the name parameter on the CLI.
pref:
description:
- - Preference or administrative difference of route (range 1-255).
- required: false
- default: null
+ - Preference or administrative difference of route (range 1-255) or keyword 'default'.
aliases:
- admin_distance
aggregate:
@@ -74,8 +67,8 @@ options:
state:
description:
- Manage the state of the resource.
- required: true
choices: ['present','absent']
+ default: 'present'
'''
EXAMPLES = '''
@@ -112,66 +105,41 @@ def reconcile_candidate(module, candidate, prefix, w):
parents = []
commands = []
+ yrc = remove_command.replace('no ', '')
if w['vrf'] == 'default':
- config = netcfg.get_section(set_command)
- if config and state == 'absent':
+ netcfg = str(netcfg).split('\n')
+ ncfg = []
+ for line in netcfg:
+ # remove ip route commands of non-default vrfs from
+ # the running config just in case the same commands
+ # exist in default and non-default vrfs
+ if ' ip route' not in line:
+ ncfg.append(line)
+ if any(yrc in s for s in ncfg) and state == 'absent':
commands = [remove_command]
- elif not config and state == 'present':
- commands = [set_command]
+ elif set_command not in ncfg and state == 'present':
+ if any(yrc in s for s in ncfg):
+ commands = [remove_command, set_command]
+ else:
+ commands = [set_command]
else:
parents = ['vrf context {0}'.format(w['vrf'])]
config = netcfg.get_section(parents)
if not isinstance(config, list):
config = config.split('\n')
config = [line.strip() for line in config]
- if set_command in config and state == 'absent':
+ if any(yrc in s for s in config) and state == 'absent':
commands = [remove_command]
elif set_command not in config and state == 'present':
- commands = [set_command]
+ if any(yrc in s for s in config):
+ commands = [remove_command, set_command]
+ else:
+ commands = [set_command]
if commands:
candidate.add(commands, parents=parents)
-def fix_prefix_to_regex(prefix):
- prefix = prefix.replace('.', r'\.').replace('/', r'\/')
- return prefix
-
-
-def get_existing(module, prefix, warnings):
- key_map = ['tag', 'pref', 'route_name', 'next_hop']
- netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
- parents = 'vrf context {0}'.format(module.params['vrf'])
- prefix_to_regex = fix_prefix_to_regex(prefix)
-
- route_regex = r'.*ip\sroute\s{0}\s(?P<next_hop>\S+)(\sname\s(?P<route_name>\S+))?(\stag\s(?P<tag>\d+))?(\s(?P<pref>\d+))?.*'.format(prefix_to_regex)
-
- if module.params['vrf'] == 'default':
- config = str(netcfg)
- else:
- config = netcfg.get_section(parents)
-
- if config:
- try:
- match_route = re.match(route_regex, config, re.DOTALL)
- group_route = match_route.groupdict()
-
- for key in key_map:
- if key not in group_route:
- group_route[key] = ''
- group_route['prefix'] = prefix
- group_route['vrf'] = module.params['vrf']
- except (AttributeError, TypeError):
- group_route = {}
- else:
- group_route = {}
- msg = ("VRF {0} didn't exist.".format(module.params['vrf']))
- if msg not in warnings:
- warnings.append(msg)
-
- return group_route
-
-
def remove_route_command(prefix, w):
return 'no ip route {0} {1}'.format(prefix, w['next_hop'])
@@ -179,11 +147,12 @@ def remove_route_command(prefix, w):
def set_route_command(prefix, w):
route_cmd = 'ip route {0} {1}'.format(prefix, w['next_hop'])
- if w['route_name']:
+ if w['route_name'] and w['route_name'] != 'default':
route_cmd += ' name {0}'.format(w['route_name'])
if w['tag']:
- route_cmd += ' tag {0}'.format(w['tag'])
- if w['pref']:
+ if w['tag'] != 'default' and w['tag'] != '0':
+ route_cmd += ' tag {0}'.format(w['tag'])
+ if w['pref'] and w['pref'] != 'default':
route_cmd += ' {0}'.format(w['pref'])
return route_cmd
diff --git a/lib/ansible/modules/network/nxos/nxos_udld.py b/lib/ansible/modules/network/nxos/nxos_udld.py
index c0a4536..4486a5b 100644
--- a/lib/ansible/modules/network/nxos/nxos_udld.py
+++ b/lib/ansible/modules/network/nxos/nxos_udld.py
@@ -34,8 +34,6 @@ author:
- Jason Edelman (@jedelman8)
notes:
- Tested against NXOSv 7.3.(0)D1(1) on VIRL
- - When C(state=absent), it unconfigures existing settings C(msg_time) and set it
- to its default value of 15. It is cleaner to always use C(state=present).
- Module will fail if the udld feature has not been previously enabled.
options:
aggressive:
@@ -46,18 +44,20 @@ options:
choices: ['enabled','disabled']
msg_time:
description:
- - Message time in seconds for UDLD packets.
+ - Message time in seconds for UDLD packets or keyword 'default'.
required: false
default: null
reset:
description:
- - Ability to reset UDLD down interfaces.
+ - Ability to reset all ports shut down by UDLD. 'state' parameter
+ cannot be 'absent' when this is present.
required: false
default: null
- choices: ['true','false']
+ choices: ['true']
state:
description:
- - Manage the state of the resource.
+ - Manage the state of the resource. When set to 'absent',
+ aggressive and msg_time are set to their default values.
required: false
default: present
choices: ['present','absent']
@@ -117,6 +117,11 @@ from ansible.module_utils.network.nxos.nxos import get_capabilities, nxos_argume
from ansible.module_utils.basic import AnsibleModule
+PARAM_TO_DEFAULT_KEYMAP = {
+ 'msg_time': '15',
+}
+
+
def execute_show_command(command, module, command_type='cli_show'):
device_info = get_capabilities(module)
network_api = device_info.get('network_api', 'nxapi')
@@ -156,42 +161,32 @@ def apply_key_map(key_map, table):
return new_dict
-def get_commands_config_udld_global(delta, reset):
- config_args = {
- 'enabled': 'udld aggressive',
- 'disabled': 'no udld aggressive',
- 'msg_time': 'udld message-time {msg_time}'
- }
+def get_commands_config_udld_global(delta, reset, existing):
commands = []
for param, value in delta.items():
if param == 'aggressive':
- if value == 'enabled':
- command = 'udld aggressive'
- elif value == 'disabled':
- command = 'no udld aggressive'
- else:
- command = config_args.get(param, 'DNE').format(**delta)
- if command and command != 'DNE':
+ command = 'udld aggressive' if value == 'enabled' else 'no udld aggressive'
commands.append(command)
- command = None
-
+ elif param == 'msg_time':
+ if value == 'default':
+ if existing.get('msg_time') != PARAM_TO_DEFAULT_KEYMAP.get('msg_time'):
+ commands.append('no udld message-time')
+ else:
+ commands.append('udld message-time ' + value)
if reset:
command = 'udld reset'
commands.append(command)
return commands
-def get_commands_remove_udld_global(delta):
- config_args = {
- 'aggressive': 'no udld aggressive',
- 'msg_time': 'no udld message-time {msg_time}',
- }
+def get_commands_remove_udld_global(existing):
commands = []
- for param, value in delta.items():
- command = config_args.get(param, 'DNE').format(**delta)
- if command and command != 'DNE':
- commands.append(command)
- command = None
+ if existing.get('aggressive') == 'enabled':
+ command = 'no udld aggressive'
+ commands.append(command)
+ if existing.get('msg_time') != PARAM_TO_DEFAULT_KEYMAP.get('msg_time'):
+ command = 'no udld message-time'
+ commands.append(command)
return commands
@@ -222,7 +217,6 @@ def main():
argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec,
- required_one_of=[['aggressive', 'msg_time', 'reset']],
supports_check_mode=True)
warnings = list()
@@ -232,20 +226,8 @@ def main():
reset = module.params['reset']
state = module.params['state']
- if (aggressive or reset) and state == 'absent':
- module.fail_json(msg="It's better to use state=present when "
- "configuring or unconfiguring aggressive mode "
- "or using reset flag. state=absent is just for "
- "when using msg_time param.")
-
- if msg_time:
- try:
- msg_time_int = int(msg_time)
- if msg_time_int < 7 or msg_time_int > 90:
- raise ValueError
- except ValueError:
- module.fail_json(msg='msg_time must be an integer'
- 'between 7 and 90')
+ if reset and state == 'absent':
+ module.fail_json(msg="state must be present when using reset flag.")
args = dict(aggressive=aggressive, msg_time=msg_time, reset=reset)
proposed = dict((k, v) for k, v in args.items() if v is not None)
@@ -259,13 +241,12 @@ def main():
commands = []
if state == 'present':
if delta:
- command = get_commands_config_udld_global(dict(delta), reset)
+ command = get_commands_config_udld_global(dict(delta), reset, existing)
commands.append(command)
elif state == 'absent':
- common = set(proposed.items()).intersection(existing.items())
- if common:
- command = get_commands_remove_udld_global(dict(common))
+ command = get_commands_remove_udld_global(existing)
+ if command:
commands.append(command)
cmds = flatten_list(commands)
diff --git a/lib/ansible/modules/network/nxos/nxos_vlan.py b/lib/ansible/modules/network/nxos/nxos_vlan.py
index 9ce9670..09e29bb 100644
--- a/lib/ansible/modules/network/nxos/nxos_vlan.py
+++ b/lib/ansible/modules/network/nxos/nxos_vlan.py
@@ -45,9 +45,10 @@ options:
- Name of VLAN.
required: false
default: null
+ - Name of VLAN or keyword 'default'.
interfaces:
description:
- - List of interfaces that should be associated to the VLAN.
+ - List of interfaces that should be associated to the VLAN or keyword 'default'.
version_added: "2.5"
associated_interfaces:
description:
@@ -79,10 +80,8 @@ options:
state:
description:
- Manage the state of the resource.
- Active and Suspend will assume the vlan is present.
- required: false
default: present
- choices: ['present','absent', 'active', 'suspend']
+ choices: ['present','absent']
mode:
description:
- Set VLAN mode to classical ethernet or fabricpath.
@@ -97,6 +96,8 @@ options:
purge:
description:
- Purge VLANs not defined in the I(aggregate) parameter.
+ This parameter can be used without aggregate as well.
+ type: bool
default: no
delay:
description:
@@ -144,6 +145,14 @@ EXAMPLES = '''
aggregate:
- { vlan_id: 4000, mode: ce }
- { vlan_id: 4001, name: vlan-4001 }
+
+- name: purge vlans - removes all other vlans except the ones mentioned in aggregate)
+ nxos_vlan:
+ aggregate:
+ - vlan_id: 1
+ - vlan_id: 4001
+ purge: yes
+
'''
RETURN = '''
@@ -173,21 +182,31 @@ def search_obj_in_list(vlan_id, lst):
def get_diff(w, obj):
c = deepcopy(w)
- entries = ('interfaces', 'associated_interfaces', 'name', 'delay', 'vlan_range')
+ entries = ('interfaces', 'associated_interfaces', 'delay', 'vlan_range')
for key in entries:
if key in c:
del c[key]
o = deepcopy(obj)
del o['interfaces']
- del o['name']
if o['vlan_id'] == w['vlan_id']:
diff_dict = dict(set(c.items()) - set(o.items()))
return diff_dict
+def is_default_name(obj, vlan_id):
+ cname = obj['name']
+ if ('VLAN' in cname):
+ vid = int(cname[4:])
+ if vid == int(vlan_id):
+ return True
+
+ return False
+
+
def map_obj_to_commands(updates, module, os_platform):
commands = list()
+ purge = module.params['purge']
want, have = updates
for w in want:
@@ -219,13 +238,13 @@ def map_obj_to_commands(updates, module, os_platform):
if not obj_in_have:
commands.append('vlan {0}'.format(vlan_id))
- if name:
+ if name and name != 'default':
commands.append('name {0}'.format(name))
if mode:
commands.append('mode {0}'.format(mode))
if vlan_state:
commands.append('state {0}'.format(vlan_state))
- if mapped_vni != 'None':
+ if mapped_vni != 'None' and mapped_vni != 'default':
commands.append('vn-segment {0}'.format(mapped_vni))
if admin_state == 'up':
commands.append('no shutdown')
@@ -233,7 +252,7 @@ def map_obj_to_commands(updates, module, os_platform):
commands.append('shutdown')
commands.append('exit')
- if interfaces:
+ if interfaces and interfaces[0] != 'default':
for i in interfaces:
commands.append('interface {0}'.format(i))
commands.append('switchport')
@@ -241,7 +260,38 @@ def map_obj_to_commands(updates, module, os_platform):
commands.append('switchport access vlan {0}'.format(vlan_id))
else:
- if interfaces:
+ diff = get_diff(w, obj_in_have)
+ if diff:
+ commands.append('vlan {0}'.format(vlan_id))
+ for key, value in diff.items():
+ if key == 'name':
+ if name != 'default':
+ if name is not None:
+ commands.append('name {0}'.format(value))
+ else:
+ if not is_default_name(obj_in_have, vlan_id):
+ commands.append('no name')
+ if key == 'vlan_state':
+ commands.append('state {0}'.format(value))
+ if key == 'mapped_vni':
+ if value == 'default':
+ if obj_in_have['mapped_vni'] != 'None':
+ commands.append('no vn-segment')
+ elif value != 'None':
+ commands.append('vn-segment {0}'.format(value))
+ if key == 'admin_state':
+ if value == 'up':
+ commands.append('no shutdown')
+ elif value == 'down':
+ commands.append('shutdown')
+ if key == 'mode':
+ commands.append('mode {0}'.format(value))
+ if len(commands) > 1:
+ commands.append('exit')
+ else:
+ del commands[:]
+
+ if interfaces and interfaces[0] != 'default':
if not obj_in_have['interfaces']:
for i in interfaces:
commands.append('vlan {0}'.format(vlan_id))
@@ -270,24 +320,21 @@ def map_obj_to_commands(updates, module, os_platform):
commands.append('switchport mode access')
commands.append('no switchport access vlan {0}'.format(vlan_id))
- else:
- diff = get_diff(w, obj_in_have)
- if diff:
- commands.append('vlan {0}'.format(vlan_id))
- for key, value in diff.items():
- if key == 'vlan_state':
- commands.append('state {0}'.format(value))
- if key == 'mapped_vni':
- if value != 'None':
- commands.append('vn-segment {0}'.format(value))
- if key == 'admin_state':
- if value == 'up':
- commands.append('no shutdown')
- elif value == 'down':
- commands.append('shutdown')
- if key == 'mode':
- commands.append('mode {0}'.format(value))
- commands.append('exit')
+ elif interfaces and interfaces[0] == 'default':
+ if obj_in_have['interfaces']:
+ for i in obj_in_have['interfaces']:
+ commands.append('vlan {0}'.format(vlan_id))
+ commands.append('exit')
+ commands.append('interface {0}'.format(i))
+ commands.append('switchport')
+ commands.append('switchport mode access')
+ commands.append('no switchport access vlan {0}'.format(vlan_id))
+
+ if purge:
+ for h in have:
+ obj_in_want = search_obj_in_list(h['vlan_id'], want)
+ if not obj_in_want:
+ commands.append('no vlan {0}'.format(h['vlan_id']))
return commands
@@ -504,9 +551,9 @@ def main():
interfaces=dict(type='list'),
associated_interfaces=dict(type='list'),
vlan_state=dict(choices=['active', 'suspend'], required=False, default='active'),
- mapped_vni=dict(required=False, type='int'),
+ mapped_vni=dict(required=False),
delay=dict(default=10, type='int'),
- state=dict(choices=['present', 'absent', 'active', 'suspend'], default='present', required=False),
+ state=dict(choices=['present', 'absent'], default='present', required=False),
admin_state=dict(choices=['up', 'down'], required=False, default='up'),
mode=dict(choices=['ce', 'fabricpath'], required=False, default='ce'),
)
diff --git a/lib/ansible/modules/network/nxos/nxos_vrf.py b/lib/ansible/modules/network/nxos/nxos_vrf.py
index 3538860..a484f3b 100644
--- a/lib/ansible/modules/network/nxos/nxos_vrf.py
+++ b/lib/ansible/modules/network/nxos/nxos_vrf.py
@@ -74,7 +74,7 @@ options:
interfaces:
description:
- List of interfaces to check the VRF has been
- configured correctly.
+ configured correctly or keyword 'default'.
version_added: 2.5
associated_interfaces:
description:
@@ -98,7 +98,7 @@ options:
choices: ['present','absent']
description:
description:
- - Description of the VRF.
+ - Description of the VRF or keyword 'default'.
required: false
default: null
delay:
@@ -257,7 +257,7 @@ def map_obj_to_commands(updates, module):
commands.append('vrf context {0}'.format(name))
for item in args:
candidate = w.get(item)
- if candidate:
+ if candidate and candidate != 'default':
cmd = item + ' ' + str(candidate)
commands.append(cmd)
if admin_state == 'up':
@@ -266,7 +266,7 @@ def map_obj_to_commands(updates, module):
commands.append('shutdown')
commands.append('exit')
- if interfaces:
+ if interfaces and interfaces[0] != 'default':
for i in interfaces:
commands.append('interface {0}'.format(i))
commands.append('no switchport')
@@ -280,7 +280,11 @@ def map_obj_to_commands(updates, module):
for item in args:
candidate = w.get(item)
- if candidate and candidate != obj_in_have.get(item):
+ if candidate == 'default':
+ if obj_in_have.get(item):
+ cmd = 'no ' + item + ' ' + obj_in_have.get(item)
+ commands.append(cmd)
+ elif candidate and candidate != obj_in_have.get(item):
cmd = item + ' ' + str(candidate)
commands.append(cmd)
if admin_state and admin_state != obj_in_have.get('admin_state'):
@@ -293,7 +297,7 @@ def map_obj_to_commands(updates, module):
commands.insert(0, 'vrf context {0}'.format(name))
commands.append('exit')
- if interfaces:
+ if interfaces and interfaces[0] != 'default':
if not obj_in_have['interfaces']:
for i in interfaces:
commands.append('vrf context {0}'.format(name))
@@ -318,6 +322,14 @@ def map_obj_to_commands(updates, module):
commands.append('interface {0}'.format(i))
commands.append('no switchport')
commands.append('no vrf member {0}'.format(name))
+ elif interfaces and interfaces[0] == 'default':
+ if obj_in_have['interfaces']:
+ for i in obj_in_have['interfaces']:
+ commands.append('vrf context {0}'.format(name))
+ commands.append('exit')
+ commands.append('interface {0}'.format(i))
+ commands.append('no switchport')
+ commands.append('no vrf member {0}'.format(name))
if purge:
existing = get_existing_vrfs(module)
diff --git a/lib/ansible/modules/network/nxos/nxos_vrf_af.py b/lib/ansible/modules/network/nxos/nxos_vrf_af.py
index 281674e..1a653e0 100644
--- a/lib/ansible/modules/network/nxos/nxos_vrf_af.py
+++ b/lib/ansible/modules/network/nxos/nxos_vrf_af.py
@@ -124,14 +124,14 @@ def main():
if current:
have = 'route-target both auto evpn' in current
- want = bool(module.params['route_target_both_auto_evpn'])
-
- if want and not have:
- commands.append('address-family %s unicast' % module.params['afi'])
- commands.append('route-target both auto evpn')
- elif have and not want:
- commands.append('address-family %s unicast' % module.params['afi'])
- commands.append('no route-target both auto evpn')
+ if module.params['route_target_both_auto_evpn'] is not None:
+ want = bool(module.params['route_target_both_auto_evpn'])
+ if want and not have:
+ commands.append('address-family %s unicast' % module.params['afi'])
+ commands.append('route-target both auto evpn')
+ elif have and not want:
+ commands.append('address-family %s unicast' % module.params['afi'])
+ commands.append('no route-target both auto evpn')
else:
commands.append('address-family %s unicast' % module.params['afi'])
diff --git a/lib/ansible/modules/network/onyx/onyx_linkagg.py b/lib/ansible/modules/network/onyx/onyx_linkagg.py
index 776d051..acfd5b4 100644
--- a/lib/ansible/modules/network/onyx/onyx_linkagg.py
+++ b/lib/ansible/modules/network/onyx/onyx_linkagg.py
@@ -109,6 +109,7 @@ class OnyxLinkAggModule(BaseOnyxModule):
CHANNEL_GROUP = 'channel-group'
MLAG_PORT_CHANNEL = 'mlag-port-channel'
MLAG_CHANNEL_GROUP = 'mlag-channel-group'
+ MLAG_SUMMARY = 'MLAG Port-Channel Summary'
LAG_TYPE = 'lag'
MLAG_TYPE = 'mlag'
@@ -225,7 +226,21 @@ class OnyxLinkAggModule(BaseOnyxModule):
def _parse_port_channels_summary(self, lag_type, lag_summary):
if lag_type == self.MLAG_TYPE:
- lag_summary = lag_summary.get('MLAG Port-Channel Summary', {})
+ if self._os_version >= self.ONYX_API_VERSION:
+ found_summary = False
+ for summary_item in lag_summary:
+ if self.MLAG_SUMMARY in summary_item:
+ lag_summary = summary_item[self.MLAG_SUMMARY]
+ if lag_summary:
+ lag_summary = lag_summary[0]
+ else:
+ lag_summary = dict()
+ found_summary = True
+ break
+ if not found_summary:
+ lag_summary = dict()
+ else:
+ lag_summary = lag_summary.get(self.MLAG_SUMMARY, dict())
for lag_key, lag_data in iteritems(lag_summary):
lag_name, state = self._extract_lag_name(lag_key)
if not lag_name:
@@ -240,6 +255,7 @@ class OnyxLinkAggModule(BaseOnyxModule):
def load_current_config(self):
self._current_config = dict()
+ self._os_version = self._get_os_version()
lag_types = set([lag_obj['type'] for lag_obj in self._required_config])
for lag_type in lag_types:
if_type = self.IF_TYPE_MAP[lag_type]
diff --git a/lib/ansible/modules/network/onyx/onyx_pfc_interface.py b/lib/ansible/modules/network/onyx/onyx_pfc_interface.py
index 9b342cb..0dacc1d 100644
--- a/lib/ansible/modules/network/onyx/onyx_pfc_interface.py
+++ b/lib/ansible/modules/network/onyx/onyx_pfc_interface.py
@@ -140,12 +140,20 @@ class OnyxPfcInterfaceModule(BaseOnyxModule):
def load_current_config(self):
# called in base class in run function
+ self._os_version = self._get_os_version()
self._current_config = dict()
pfc_config = self._get_pfc_config()
if not pfc_config:
return
- if 'Table 2' in pfc_config:
- pfc_config = pfc_config['Table 2']
+ if self._os_version >= self.ONYX_API_VERSION:
+ if len(pfc_config) >= 3:
+ pfc_config = pfc_config[2]
+ else:
+ pfc_config = dict()
+ else:
+ if 'Table 2' in pfc_config:
+ pfc_config = pfc_config['Table 2']
+
for if_name, if_pfc_data in iteritems(pfc_config):
match = self.PFC_IF_REGEX.match(if_name)
if not match:
diff --git a/lib/ansible/modules/network/onyx/onyx_vlan.py b/lib/ansible/modules/network/onyx/onyx_vlan.py
index e1bedf4..ae7bbe7 100644
--- a/lib/ansible/modules/network/onyx/onyx_vlan.py
+++ b/lib/ansible/modules/network/onyx/onyx_vlan.py
@@ -138,6 +138,8 @@ class OnyxVlanModule(BaseOnyxModule):
self._required_config.append(params)
def _create_vlan_data(self, vlan_id, vlan_data):
+ if self._os_version >= self.ONYX_API_VERSION:
+ vlan_data = vlan_data[0]
return {
'vlan_id': vlan_id,
'name': self.get_config_attr(vlan_data, 'Name')
@@ -148,6 +150,7 @@ class OnyxVlanModule(BaseOnyxModule):
def load_current_config(self):
# called in base class in run function
+ self._os_version = self._get_os_version()
self._current_config = dict()
vlan_config = self._get_vlan_config()
if not vlan_config:
diff --git a/lib/ansible/modules/network/vyos/vyos_config.py b/lib/ansible/modules/network/vyos/vyos_config.py
index d533977..848ae6f 100644
--- a/lib/ansible/modules/network/vyos/vyos_config.py
+++ b/lib/ansible/modules/network/vyos/vyos_config.py
@@ -68,7 +68,9 @@ options:
- The C(backup) argument will backup the current devices 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
+ in the playbook root directory or role root directory, if
+ playbook is part of an ansible role. If the directory does not
+ exist, it is created.
required: false
default: false
choices: ['yes', 'no']
diff --git a/lib/ansible/modules/packaging/os/zypper.py b/lib/ansible/modules/packaging/os/zypper.py
index 15281e0..b998596 100644
--- a/lib/ansible/modules/packaging/os/zypper.py
+++ b/lib/ansible/modules/packaging/os/zypper.py
@@ -313,6 +313,9 @@ def get_cmd(m, subcommand):
if (is_install or is_refresh) and m.params['disable_gpg_check']:
cmd.append('--no-gpg-checks')
+ if subcommand == 'search':
+ cmd.append('--disable-repositories')
+
cmd.append(subcommand)
if subcommand not in ['patch', 'dist-upgrade'] and not is_refresh:
cmd.extend(['--type', m.params['type']])
diff --git a/lib/ansible/modules/source_control/github_hooks.py b/lib/ansible/modules/source_control/github_hooks.py
index ef5c19b..e1480f6 100644
--- a/lib/ansible/modules/source_control/github_hooks.py
+++ b/lib/ansible/modules/source_control/github_hooks.py
@@ -80,15 +80,16 @@ EXAMPLES = '''
delegate_to: localhost
'''
-import base64
import json
+import base64
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
+from ansible.module_utils._text import to_bytes
def request(module, url, user, oauthkey, data='', method='GET'):
- auth = base64.encodestring('%s:%s' % (user, oauthkey)).replace('\n', '')
+ auth = base64.b64encode(to_bytes('%s:%s' % (user, oauthkey)).replace('\n', ''))
headers = {
'Authorization': 'Basic %s' % auth,
}
diff --git a/lib/ansible/modules/system/interfaces_file.py b/lib/ansible/modules/system/interfaces_file.py
index 897d69d..a6a5d91 100755
--- a/lib/ansible/modules/system/interfaces_file.py
+++ b/lib/ansible/modules/system/interfaces_file.py
@@ -150,6 +150,7 @@ import re
import tempfile
from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_bytes
def lineDict(line):
@@ -298,11 +299,11 @@ def setInterfaceOption(module, lines, iface, option, raw_value, state):
if option in ["pre-up", "up", "down", "post-up"] and value is not None and value != "None":
for target_option in filter(lambda i: i['value'] == value, target_options):
changed = True
- lines = list(filter(lambda l: l != target_option, lines))
+ lines = list(filter(lambda ln: ln != target_option, lines))
else:
changed = True
for target_option in target_options:
- lines = list(filter(lambda l: l != target_option, lines))
+ lines = list(filter(lambda ln: ln != target_option, lines))
else:
module.fail_json(msg="Error: unsupported state %s, has to be either present or absent" % state)
@@ -330,7 +331,7 @@ def write_changes(module, lines, dest):
tmpfd, tmpfile = tempfile.mkstemp()
f = os.fdopen(tmpfd, 'wb')
- f.writelines(lines)
+ f.write(to_bytes(''.join(lines), errors='surrogate_or_strict'))
f.close()
module.atomic_move(tmpfile, os.path.realpath(dest))
diff --git a/lib/ansible/modules/system/puppet.py b/lib/ansible/modules/system/puppet.py
index a789737..631cd6e 100644
--- a/lib/ansible/modules/system/puppet.py
+++ b/lib/ansible/modules/system/puppet.py
@@ -111,16 +111,16 @@ def _get_facter_dir():
def _write_structured_data(basedir, basename, data):
if not os.path.exists(basedir):
os.makedirs(basedir)
- file_path = os.path.join(basedir, "{0}.json".format(basename))
- # This is more complex than you might normally expect because we want to
- # open the file with only u+rw set. Also, we use the stat constants
- # because ansible still supports python 2.4 and the octal syntax changed
- out_file = os.fdopen(
- os.open(
- file_path, os.O_CREAT | os.O_WRONLY,
- stat.S_IRUSR | stat.S_IWUSR), 'w')
- out_file.write(json.dumps(data).encode('utf8'))
- out_file.close()
+ file_path = os.path.join(basedir, "{0}.json".format(basename))
+ # This is more complex than you might normally expect because we want to
+ # open the file with only u+rw set. Also, we use the stat constants
+ # because ansible still supports python 2.4 and the octal syntax changed
+ out_file = os.fdopen(
+ os.open(
+ file_path, os.O_CREAT | os.O_WRONLY,
+ stat.S_IRUSR | stat.S_IWUSR), 'w')
+ out_file.write(json.dumps(data).encode('utf8'))
+ out_file.close()
def main():
diff --git a/lib/ansible/modules/system/user.py b/lib/ansible/modules/system/user.py
index f1b023a..06cf1ea 100644
--- a/lib/ansible/modules/system/user.py
+++ b/lib/ansible/modules/system/user.py
@@ -79,12 +79,10 @@ options:
version_added: "2.0"
password:
description:
- - Optionally set the user's password to this crypted value. See
- the user example in the github examples directory for what this looks
- like in a playbook. See U(http://docs.ansible.com/ansible/faq.html#how-do-i-generate-crypted-passwords-for-the-user-module)
+ - Optionally set the user's password to this crypted value.
+ - On Darwin/OS X systems, this value has to be cleartext. Beware of security issues.
+ - See U(http://docs.ansible.com/ansible/faq.html#how-do-i-generate-crypted-passwords-for-the-user-module)
for details on various ways to generate these password values.
- Note on Darwin system, this value has to be cleartext.
- Beware of security issues.
state:
description:
- Whether the account should exist or not, taking action if the state is different from what is stated.
@@ -905,9 +903,8 @@ class FreeBsdUser(User):
cmd.append(','.join(new_groups))
if self.expires:
- days = (time.mktime(self.expires) - time.time()) // 86400
cmd.append('-e')
- cmd.append(str(int(days)))
+ cmd.append(str(int(time.mktime(self.expires))))
# modify the user if cmd will do anything
if cmd_len != len(cmd):
diff --git a/lib/ansible/modules/web_infrastructure/jenkins_script.py b/lib/ansible/modules/web_infrastructure/jenkins_script.py
index 7efa492..de92a79 100644
--- a/lib/ansible/modules/web_infrastructure/jenkins_script.py
+++ b/lib/ansible/modules/web_infrastructure/jenkins_script.py
@@ -124,7 +124,7 @@ def is_csrf_protection_enabled(module):
module.params['url'] + '/api/json',
method='GET')
if info["status"] != 200:
- module.fail_json(msg="HTTP error " + str(info["status"]) + " " + info["msg"])
+ module.fail_json(msg="HTTP error " + str(info["status"]) + " " + info["msg"], output='')
content = to_native(resp.read())
return json.loads(content).get('useCrumbs', False)
@@ -135,7 +135,7 @@ def get_crumb(module):
module.params['url'] + '/crumbIssuer/api/json',
method='GET')
if info["status"] != 200:
- module.fail_json(msg="HTTP error " + str(info["status"]) + " " + info["msg"])
+ module.fail_json(msg="HTTP error " + str(info["status"]) + " " + info["msg"], output='')
content = to_native(resp.read())
return json.loads(content)
@@ -157,14 +157,17 @@ def main():
if module.params['user'] is not None:
if module.params['password'] is None:
- module.fail_json(msg="password required when user provided")
+ module.fail_json(msg="password required when user provided", output='')
module.params['url_username'] = module.params['user']
module.params['url_password'] = module.params['password']
module.params['force_basic_auth'] = True
if module.params['args'] is not None:
from string import Template
- script_contents = Template(module.params['script']).substitute(module.params['args'])
+ try:
+ script_contents = Template(module.params['script']).substitute(module.params['args'])
+ except KeyError as err:
+ module.fail_json(msg="Error with templating variable: %s" % err, output='')
else:
script_contents = module.params['script']
@@ -181,12 +184,12 @@ def main():
timeout=module.params['timeout'])
if info["status"] != 200:
- module.fail_json(msg="HTTP error " + str(info["status"]) + " " + info["msg"])
+ module.fail_json(msg="HTTP error " + str(info["status"]) + " " + info["msg"], output='')
result = to_native(resp.read())
if 'Exception:' in result and 'at java.lang.Thread' in result:
- module.fail_json(msg="script failed with stacktrace:\n " + result)
+ module.fail_json(msg="script failed with stacktrace:\n " + result, output='')
module.exit_json(
output=result,
diff --git a/lib/ansible/modules/web_infrastructure/jira.py b/lib/ansible/modules/web_infrastructure/jira.py
index 8701421..9d84bb7 100644
--- a/lib/ansible/modules/web_infrastructure/jira.py
+++ b/lib/ansible/modules/web_infrastructure/jira.py
@@ -233,6 +233,7 @@ EXAMPLES = """
import base64
import json
import sys
+from ansible.module_utils._text import to_text, to_bytes
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
@@ -249,7 +250,8 @@ def request(url, user, passwd, timeout, data=None, method=None):
# resulting in unexpected results. To work around this we manually
# inject the basic-auth header up-front to ensure that JIRA treats
# the requests as authorized for this user.
- auth = base64.encodestring('%s:%s' % (user, passwd)).replace('\n', '')
+ auth = to_text(base64.b64encode(to_bytes('{0}:{1}'.format(user, passwd), errors='surrogate_or_strict')))
+
response, info = fetch_url(module, url, data=data, method=method, timeout=timeout,
headers={'Content-Type': 'application/json',
'Authorization': "Basic %s" % auth})
diff --git a/lib/ansible/modules/web_infrastructure/letsencrypt.py b/lib/ansible/modules/web_infrastructure/letsencrypt.py
index 07bfaa2..195220f 100644
--- a/lib/ansible/modules/web_infrastructure/letsencrypt.py
+++ b/lib/ansible/modules/web_infrastructure/letsencrypt.py
@@ -24,8 +24,10 @@ description:
free, automated, and open certificate authority (CA), run for the
public's benefit. For details see U(https://letsencrypt.org). The current
implementation supports the http-01, tls-sni-02 and dns-01 challenges."
- - "To use this module, it has to be executed at least twice. Either as two
- different tasks in the same run or during multiple runs."
+ - "To use this module, it has to be executed twice. Either as two
+ different tasks in the same run or during two runs. Note that the output
+ of the first run needs to be recorded and passed to the second run as the
+ module argument C(data)."
- "Between these two tasks you have to fulfill the required steps for the
chosen challenge by whatever means necessary. For http-01 that means
creating the necessary challenge file on the destination webserver. For
@@ -33,7 +35,8 @@ description:
you to create a SSL certificate with the appropriate subjectAlternativeNames.
It is I(not) the responsibility of this module to perform these steps."
- "For details on how to fulfill these challenges, you might have to read through
- U(https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-8)"
+ U(https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-8).
+ Also, consider the examples provided for this module."
- "Although the defaults are chosen so that the module can be used with
the Let's Encrypt CA, the module can be used with any service using the ACME
v1 or v2 protocol. I(Warning): ACME v2 support is currently experimental, as
@@ -122,9 +125,10 @@ options:
aliases: ['src']
data:
description:
- - "The data to validate ongoing challenges."
+ - "The data to validate ongoing challenges. This must be specified for
+ the second run of the module only."
- "The value that must be used here will be provided by a previous use
- of this module."
+ of this module. See the examples for more details."
dest:
description:
- "The destination file for the certificate."
@@ -598,40 +602,44 @@ class ACMEAccount(object):
}
elif account_key_type == 'ec':
pub_data = re.search(
- r"pub:\s*\n\s+04:([a-f0-9\:\s]+?)\nASN1 OID: (\S+)\nNIST CURVE: (\S+)",
+ r"pub:\s*\n\s+04:([a-f0-9\:\s]+?)\nASN1 OID: (\S+)(?:\nNIST CURVE: (\S+))?",
to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL)
if pub_data is None:
return 'cannot parse elliptic curve key', {}
pub_hex = binascii.unhexlify(re.sub(r"(\s|:)", "", pub_data.group(1)).encode("utf-8"))
- curve = pub_data.group(3).lower()
- if curve == 'p-256':
+ asn1_oid_curve = pub_data.group(2).lower()
+ nist_curve = pub_data.group(3).lower() if pub_data.group(3) else None
+ if asn1_oid_curve == 'prime256v1' or nist_curve == 'p-256':
bits = 256
alg = 'ES256'
hash = 'sha256'
point_size = 32
- elif curve == 'p-384':
+ curve = 'P-256'
+ elif asn1_oid_curve == 'secp384r1' or nist_curve == 'p-384':
bits = 384
alg = 'ES384'
hash = 'sha384'
point_size = 48
- elif curve == 'p-521':
+ curve = 'P-384'
+ elif asn1_oid_curve == 'secp521r1' or nist_curve == 'p-521':
# Not yet supported on Let's Encrypt side, see
# https://github.com/letsencrypt/boulder/issues/2217
bits = 521
alg = 'ES512'
hash = 'sha512'
point_size = 66
+ curve = 'P-521'
else:
- return 'unknown elliptic curve: %s' % curve, {}
+ return 'unknown elliptic curve: %s / %s' % (asn1_oid_curve, nist_curve), {}
bytes = (bits + 7) // 8
if len(pub_hex) != 2 * bytes:
- return 'bad elliptic curve point (%s)' % curve, {}
+ return 'bad elliptic curve point (%s / %s)' % (asn1_oid_curve, nist_curve), {}
return None, {
'type': 'ec',
'alg': alg,
'jwk': {
"kty": "EC",
- "crv": curve.upper(),
+ "crv": curve,
"x": nopad_b64(pub_hex[:bytes]),
"y": nopad_b64(pub_hex[bytes:]),
},
@@ -1112,10 +1120,12 @@ class ACMEClient(object):
if info['status'] not in [201]:
self.module.fail_json(msg="Error new order: CODE: {0} RESULT: {1}".format(info['status'], result))
- for identifier, auth_uri in zip(result['identifiers'], result['authorizations']):
- domain = identifier['value']
+ for auth_uri in result['authorizations']:
auth_data = simple_get(self.module, auth_uri)
auth_data['uri'] = auth_uri
+ domain = auth_data['identifier']['value']
+ if auth_data.get('wildcard', False):
+ domain = '*.{0}'.format(domain)
self.authorizations[domain] = auth_data
self.order_uri = info['location']
diff --git a/lib/ansible/modules/windows/setup.ps1 b/lib/ansible/modules/windows/setup.ps1
index ae618d8..6fc9f27 100644
--- a/lib/ansible/modules/windows/setup.ps1
+++ b/lib/ansible/modules/windows/setup.ps1
@@ -29,24 +29,32 @@ Function Get-MachineSid {
# only accessible by the Local System account. This method get's the local
# admin account (ends with -500) and lops it off to get the machine sid.
+ $admins_sid = "S-1-5-32-544"
+ $admin_group = ([Security.Principal.SecurityIdentifier]$admins_sid).Translate([Security.Principal.NTAccount]).Value
+
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$principal_context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine)
- $user_principal = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal($principal_context)
- $searcher = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalSearcher($user_principal)
- $users = $searcher.FindAll() | Where-Object { $_.Sid -like "*-500" }
+ $group_principal = New-Object -TypeName System.DirectoryServices.AccountManagement.GroupPrincipal($principal_context, $admin_group)
+ $searcher = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalSearcher($group_principal)
+ $groups = $searcher.FindOne()
$machine_sid = $null
- if ($users -ne $null) {
- $machine_sid = $users.Sid.AccountDomainSid.Value
+ foreach ($user in $groups.Members) {
+ $user_sid = $user.Sid
+ if ($user_sid.Value.EndsWith("-500")) {
+ $machine_sid = $user_sid.AccountDomainSid.Value
+ break
+ }
}
+
return $machine_sid
}
$cim_instances = @{}
-Function Get-LazyCimInstance([string]$instance_name) {
+Function Get-LazyCimInstance([string]$instance_name, [string]$namespace="Root\CIMV2") {
if(-not $cim_instances.ContainsKey($instance_name)) {
- $cim_instances[$instance_name] = $(Get-CimInstance $instance_name)
+ $cim_instances[$instance_name] = $(Get-CimInstance -Namespace $namespace -ClassName $instance_name)
}
return $cim_instances[$instance_name]
@@ -229,15 +237,26 @@ if($gather_subset.Contains('facter')) {
if($gather_subset.Contains('interfaces')) {
$netcfg = Get-LazyCimInstance Win32_NetworkAdapterConfiguration
-
$ActiveNetcfg = @()
$ActiveNetcfg += $netcfg | where {$_.ipaddress -ne $null}
+ $namespaces = Get-LazyCimInstance __Namespace -namespace root
+ if ($namespaces | Where-Object { $_.Name -eq "StandardCimv" }) {
+ $net_adapters = Get-LazyCimInstance MSFT_NetAdapter -namespace Root\StandardCimv2
+ $guid_key = "InterfaceGUID"
+ $name_key = "Name"
+ } else {
+ $net_adapters = Get-LazyCimInstance Win32_NetworkAdapter
+ $guid_key = "GUID"
+ $name_key = "NetConnectionID"
+ }
+
$formattednetcfg = @()
foreach ($adapter in $ActiveNetcfg)
{
$thisadapter = @{
default_gateway = $null
+ connection_name = $null
dns_domain = $adapter.dnsdomain
interface_index = $adapter.InterfaceIndex
interface_name = $adapter.description
@@ -248,6 +267,10 @@ if($gather_subset.Contains('interfaces')) {
{
$thisadapter.default_gateway = $adapter.DefaultIPGateway[0].ToString()
}
+ $net_adapter = $net_adapters | Where-Object { $_.$guid_key -eq $adapter.SettingID }
+ if ($net_adapter) {
+ $thisadapter.connection_name = $net_adapter.$name_key
+ }
$formattednetcfg += $thisadapter
}
diff --git a/lib/ansible/modules/windows/win_certificate_store.ps1 b/lib/ansible/modules/windows/win_certificate_store.ps1
index d567d5a..5fb2c05 100644
--- a/lib/ansible/modules/windows/win_certificate_store.ps1
+++ b/lib/ansible/modules/windows/win_certificate_store.ps1
@@ -21,7 +21,7 @@ $store_name = Get-AnsibleParam -obj $params -name "store_name" -type "str" -defa
$store_location = Get-AnsibleParam -obj $params -name "store_location" -type "str" -default "LocalMachine" -validateset $store_location_values
$password = Get-AnsibleParam -obj $params -name "password" -type "str"
$key_exportable = Get-AnsibleParam -obj $params -name "key_exportable" -type "bool" -default $true
-$key_storage = Get-AnsibleParam -obj $param -name "key_storage" -type "str" -default "default" -validateset "default", "machine", "user"
+$key_storage = Get-AnsibleParam -obj $params -name "key_storage" -type "str" -default "default" -validateset "default", "machine", "user"
$file_type = Get-AnsibleParam -obj $params -name "file_type" -type "str" -default "der" -validateset "der", "pem", "pkcs12"
$result = @{
diff --git a/lib/ansible/modules/windows/win_certificate_store.py b/lib/ansible/modules/windows/win_certificate_store.py
index d713723..6be7607 100644
--- a/lib/ansible/modules/windows/win_certificate_store.py
+++ b/lib/ansible/modules/windows/win_certificate_store.py
@@ -55,6 +55,14 @@ options:
description:
- The store name to use when importing a certificate or searching for a
certificate.
+ - "C(AddressBook): The X.509 certificate store for other users"
+ - "C(AuthRoot): The X.509 certificate store for third-party certificate authorities (CAs)"
+ - "C(CertificateAuthority): The X.509 certificate store for intermediate certificate authorities (CAs)"
+ - "C(Disallowed): The X.509 certificate store for revoked certificates"
+ - "C(My): The X.509 certificate store for personal certificates"
+ - "C(Root): The X.509 certificate store for trusted root certificate authorities (CAs)"
+ - "C(TrustedPeople): The X.509 certificate store for directly trusted people and resources"
+ - "C(TrustedPublisher): The X.509 certificate store for directly trusted publishers"
default: My
choices:
- AddressBook
diff --git a/lib/ansible/modules/windows/win_regedit.ps1 b/lib/ansible/modules/windows/win_regedit.ps1
index 0e03908..1ac83ef 100644
--- a/lib/ansible/modules/windows/win_regedit.ps1
+++ b/lib/ansible/modules/windows/win_regedit.ps1
@@ -562,6 +562,7 @@ $key_prefix[$path]
} finally {
if ($hive) {
[GC]::Collect()
+ [GC]::WaitForPendingFinalizers()
try {
[Ansible.RegistryUtil]::UnloadHive("ANSIBLE")
} catch [System.ComponentModel.Win32Exception] {
diff --git a/lib/ansible/modules/windows/win_service.ps1 b/lib/ansible/modules/windows/win_service.ps1
index 97aa44e..cdd1232 100644
--- a/lib/ansible/modules/windows/win_service.ps1
+++ b/lib/ansible/modules/windows/win_service.ps1
@@ -1,23 +1,11 @@
#!powershell
# This file is part of Ansible
-#
+
# Copyright 2014, Chris Hoffman <choffman@chathamfinancial.com>
-#
-# 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 <http://www.gnu.org/licenses/>.
-
-# WANT_JSON
-# POWERSHELL_COMMON
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#Requires -Module Ansible.ModuleUtils.Legacy
+#Requires -Module Ansible.ModuleUtils.SID
$ErrorActionPreference = "Stop"
@@ -39,11 +27,28 @@ $username = Get-AnsibleParam -obj $params -name 'username' -type 'str'
$result = @{
changed = $false
- warnings = @()
}
-if ($username -ne $null -and $password -eq $null) {
- Fail-Json $result "The argument 'password' must be supplied with 'username'"
+# parse the username to SID and back so we get the full username with domain in a way WMI understands
+if ($username -ne $null) {
+ if ($username -eq "LocalSystem") {
+ $username_sid = "S-1-5-18"
+ } else {
+ $username_sid = Convert-ToSID -account_name $username
+ }
+
+ # the SYSTEM account is a special beast, Win32_Service Change requires StartName to be LocalSystem
+ # to specify LocalSystem/NT AUTHORITY\SYSTEM
+ if ($username_sid -eq "S-1-5-18") {
+ $username = "LocalSystem"
+ $password = $null
+ } else {
+ # Win32_Service, password must be "" and not $null when setting to LocalService or NetworkService
+ if ($username_sid -in @("S-1-5-19", "S-1-5-20")) {
+ $password = ""
+ }
+ $username = Convert-FromSID -sid $username_sid
+ }
}
if ($password -ne $null -and $username -eq $null) {
Fail-Json $result "The argument 'username' must be supplied with 'password'"
@@ -57,8 +62,8 @@ if ($path -ne $null) {
Function Get-ServiceInfo($name) {
# Need to get new objects so we have the latest info
- $svc = Get-Service -Name $name
- $wmi_svc = Get-WmiObject Win32_Service | Where-Object { $_.Name -eq $svc.Name }
+ $svc = Get-Service | Where-Object { $_.Name -eq $name -or $_.DisplayName -eq $name }
+ $wmi_svc = Get-CimInstance -ClassName Win32_Service -Filter "name='$($svc.Name)'"
# Delayed start_mode is in reality Automatic (Delayed), need to check reg key for type
$delayed = Get-DelayedStatus -name $svc.Name
@@ -91,8 +96,8 @@ Function Get-ServiceInfo($name) {
$result.start_mode = $actual_start_mode
$result.path = $wmi_svc.PathName
$result.description = $description
- $result.username = $wmi_svc.startname
- $result.desktop_interact = (ConvertTo-Bool $wmi_svc.DesktopInteract)
+ $result.username = $wmi_svc.StartName
+ $result.desktop_interact = $wmi_svc.DesktopInteract
$result.dependencies = $existing_dependencies
$result.depended_by = $existing_depended_by
$result.can_pause_and_continue = $svc.CanPauseAndContinue
@@ -132,7 +137,7 @@ Function Get-WmiErrorMessage($return_value) {
Function Get-DelayedStatus($name) {
$delayed_key = "HKLM:\System\CurrentControlSet\Services\$name"
try {
- $delayed = ConvertTo-Bool ((Get-ItemProperty -Path $delayed_key).DelayedAutostart)
+ $delayed = ConvertTo-Bool ((Get-ItemProperty -LiteralPath $delayed_key).DelayedAutostart)
} catch {
$delayed = $false
}
@@ -146,14 +151,14 @@ Function Set-ServiceStartMode($svc, $start_mode) {
$delayed_key = "HKLM:\System\CurrentControlSet\Services\$($svc.Name)"
# Original start up type was auto (delayed) and we want auto, need to removed delayed key
if ($start_mode -eq 'auto' -and $result.start_mode -eq 'delayed') {
- Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 0 -Type DWORD -WhatIf:$check_mode
+ Set-ItemProperty -LiteralPath $delayed_key -Name "DelayedAutostart" -Value 0 -WhatIf:$check_mode
# Original start up type was auto and we want auto (delayed), need to add delayed key
} elseif ($start_mode -eq 'delayed' -and $result.start_mode -eq 'auto') {
- Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 1 -Type DWORD -WhatIf:$check_mode
+ Set-ItemProperty -LiteralPath $delayed_key -Name "DelayedAutostart" -Value 1 -WhatIf:$check_mode
# Original start up type was not auto or auto (delayed), need to change to auto and add delayed key
} elseif ($start_mode -eq 'delayed') {
$svc | Set-Service -StartupType "auto" -WhatIf:$check_mode
- Set-ItemProperty -Path $delayed_key -Name "DelayedAutostart" -Value 1 -Type DWORD -WhatIf:$check_mode
+ Set-ItemProperty -LiteralPath $delayed_key -Name "DelayedAutostart" -Value 1 -WhatIf:$check_mode
# Original start up type was not what we were looking for, just change to that type
} else {
$svc | Set-Service -StartupType $start_mode -WhatIf:$check_mode
@@ -166,13 +171,30 @@ Function Set-ServiceStartMode($svc, $start_mode) {
}
}
-Function Set-ServiceAccount($wmi_svc, $username, $password) {
- if ($result.username -ne $username) {
+Function Set-ServiceAccount($wmi_svc, $username_sid, $username, $password) {
+ if ($result.username -eq "LocalSystem") {
+ $actual_sid = "S-1-5-18"
+ } else {
+ $actual_sid = Convert-ToSID -account_name $result.username
+ }
+
+ if ($actual_sid -ne $username_sid) {
+ $change_arguments = @{
+ StartName = $username
+ StartPassword = $password
+ DesktopInteract = $result.desktop_interact
+ }
+ # need to disable desktop interact when not using the SYSTEM account
+ if ($username_sid -ne "S-1-5-18") {
+ $change_arguments.DesktopInteract = $false
+ }
+
#WMI.Change doesn't support -WhatIf, cannot fully test with check_mode
if (-not $check_mode) {
- $return = $wmi_svc.Change($null,$null,$null,$null,$null,$false,$username,$password,$null,$null,$null)
+ $return = $wmi_svc | Invoke-CimMethod -MethodName Change -Arguments $change_arguments
if ($return.ReturnValue -ne 0) {
- Fail-Json $result "$($return.ReturnValue): $(Get-WmiErrorMessage -return_value $return.ReturnValue)"
+ $error_msg = Get-WmiErrorMessage -return_value $result.ReturnValue
+ Fail-Json -obj $result -message "Failed to set service account to $($username): $($return.ReturnValue) - $error_msg"
}
}
@@ -183,9 +205,10 @@ Function Set-ServiceAccount($wmi_svc, $username, $password) {
Function Set-ServiceDesktopInteract($wmi_svc, $desktop_interact) {
if ($result.desktop_interact -ne $desktop_interact) {
if (-not $check_mode) {
- $return = $wmi_svc.Change($null,$null,$null,$null,$null,$desktop_interact,$null,$null,$null,$null,$null)
+ $return = $wmi_svc | Invoke-CimMethod -MethodName Change -Arguments @{DesktopInteract = $desktop_interact}
if ($return.ReturnValue -ne 0) {
- Fail-Json $result "$($return.ReturnValue): $(Get-WmiErrorMessage -return_value $return.ReturnValue)"
+ $error_msg = Get-WmiErrorMessage -return_value $return.ReturnValue
+ Fail-Json -obj $result -message "Failed to set desktop interact $($desktop_interact): $($return.ReturnValue) - $error_msg"
}
}
@@ -220,7 +243,7 @@ Function Set-ServiceDescription($svc, $description) {
Function Set-ServicePath($name, $path) {
if ($result.path -ne $path) {
try {
- Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Services\$name" -Name ImagePath -Value $path -WhatIf:$check_mode
+ Set-ItemProperty -LiteralPath "HKLM:\System\CurrentControlSet\Services\$name" -Name ImagePath -Value $path -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
@@ -266,9 +289,11 @@ Function Set-ServiceDependencies($wmi_svc, $dependency_action, $dependencies) {
if ($will_change -eq $true) {
if (-not $check_mode) {
- $return = $wmi_svc.Change($null,$null,$null,$null,$null,$null,$null,$null,$null,$null,$new_dependencies)
+ $return = $wmi_svc | Invoke-CimMethod -MethodName Change -Arguments @{ServiceDependencies = $new_dependencies}
if ($return.ReturnValue -ne 0) {
- Fail-Json $result "$($return.ReturnValue): $(Get-WmiErrorMessage -return_value $return.ReturnValue)"
+ $error_msg = Get-WmiErrorMessage -return_value $return.ReturnValue
+ $dep_string = $new_dependencies -join ", "
+ Fail-Json -obj $result -message "Failed to set service dependencies $($dep_string): $($return.ReturnValue) - $error_msg"
}
}
@@ -280,13 +305,13 @@ Function Set-ServiceState($svc, $wmi_svc, $state) {
if ($state -eq "started" -and $result.state -ne "running") {
if ($result.state -eq "paused") {
try {
- Resume-Service -Name $svc.Name -WhatIf:$check_mode
+ $svc | Resume-Service -WhatIf:$check_mode
} catch {
Fail-Json $result "failed to start service from paused state $($svc.Name): $($_.Exception.Message)"
}
} else {
try {
- Start-Service -Name $svc.Name -WhatIf:$check_mode
+ $svc | Start-Service -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
@@ -297,7 +322,7 @@ Function Set-ServiceState($svc, $wmi_svc, $state) {
if ($state -eq "stopped" -and $result.state -ne "stopped") {
try {
- Stop-Service -Name $svc.Name -Force:$force_dependent_services -WhatIf:$check_mode
+ $svc | Stop-Service -Force:$force_dependent_services -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
@@ -307,7 +332,7 @@ Function Set-ServiceState($svc, $wmi_svc, $state) {
if ($state -eq "restarted") {
try {
- Restart-Service -Name $svc.Name -Force:$force_dependent_services -WhatIf:$check_mode
+ $svc | Restart-Service -Force:$force_dependent_services -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
@@ -322,7 +347,7 @@ Function Set-ServiceState($svc, $wmi_svc, $state) {
}
try {
- Suspend-Service -Name $svc.Name -WhatIf:$check_mode
+ $svc | Suspend-Service -WhatIf:$check_mode
} catch {
Fail-Json $result "failed to pause service $($svc.Name): $($_.Exception.Message)"
}
@@ -331,14 +356,15 @@ Function Set-ServiceState($svc, $wmi_svc, $state) {
if ($state -eq "absent") {
try {
- Stop-Service -Name $svc.Name -Force:$force_dependent_services -WhatIf:$check_mode
+ $svc | Stop-Service -Force:$force_dependent_services -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
if (-not $check_mode) {
- $return = $wmi_svc.Delete()
+ $return = $wmi_svc | Invoke-CimMethod -MethodName Delete
if ($return.ReturnValue -ne 0) {
- Fail-Json $result "$($return.ReturnValue): $(Get-WmiErrorMessage -return_value $return.ReturnValue)"
+ $error_msg = Get-WmiErrorMessage -return_value $return.ReturnValue
+ Fail-Json -obj $result -message "Failed to delete service $($svc.Name): $($return.ReturnValue) - $error_msg"
}
}
@@ -347,7 +373,7 @@ Function Set-ServiceState($svc, $wmi_svc, $state) {
}
Function Set-ServiceConfiguration($svc) {
- $wmi_svc = Get-WmiObject Win32_Service | Where-Object { $_.Name -eq $svc.Name }
+ $wmi_svc = Get-CimInstance -ClassName Win32_Service -Filter "name='$($svc.Name)'"
Get-ServiceInfo -name $svc.Name
if ($desktop_interact -eq $true -and (-not ($result.username -eq 'LocalSystem' -or $username -eq 'LocalSystem'))) {
Fail-Json $result "Can only set desktop_interact to true when service is run with/or 'username' equals 'LocalSystem'"
@@ -358,7 +384,7 @@ Function Set-ServiceConfiguration($svc) {
}
if ($username -ne $null) {
- Set-ServiceAccount -wmi_svc $wmi_svc -username $username -password $password
+ Set-ServiceAccount -wmi_svc $wmi_svc -username_sid $username_sid -username $username -password $password
}
if ($display_name -ne $null) {
@@ -386,7 +412,9 @@ Function Set-ServiceConfiguration($svc) {
}
}
-$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
+# need to use Where-Object as -Name doesn't work with [] in the service name
+# https://github.com/ansible/ansible/issues/37621
+$svc = Get-Service | Where-Object { $_.Name -eq $name -or $_.DisplayName -eq $name }
if ($svc) {
Set-ServiceConfiguration -svc $svc
} else {
@@ -401,7 +429,7 @@ if ($svc) {
}
$result.changed = $true
- $svc = Get-Service -Name $name
+ $svc = Get-Service | Where-Object { $_.Name -eq $name }
Set-ServiceConfiguration -svc $svc
} else {
# We will only reach here if the service is installed and the state is not absent
@@ -425,14 +453,12 @@ if ($svc) {
if ($state -eq 'absent') {
# Recreate result so it doesn't have the extra meta data now that is has been deleted
$changed = $result.changed
- $warnings = $result.warnings
$result = @{
changed = $changed
- warnings = $warnings
exists = $false
}
} elseif ($svc -ne $null) {
Get-ServiceInfo -name $name
}
-Exit-Json $result
+Exit-Json -obj $result
diff --git a/lib/ansible/modules/windows/win_service.py b/lib/ansible/modules/windows/win_service.py
index af58736..7ebc412 100644
--- a/lib/ansible/modules/windows/win_service.py
+++ b/lib/ansible/modules/windows/win_service.py
@@ -107,7 +107,9 @@ options:
username:
description:
- The username to set the service to start as.
- - This and the C(password) argument must be supplied together.
+ - This and the C(password) argument must be supplied together when using
+ a local or domain account.
+ - Set to C(LocalSystem) to use the SYSTEM account.
version_added: "2.3"
notes:
- For non-Windows targets, use the M(service) module instead.
diff --git a/lib/ansible/modules/windows/win_uri.ps1 b/lib/ansible/modules/windows/win_uri.ps1
index f3a2343..7d04055 100644
--- a/lib/ansible/modules/windows/win_uri.ps1
+++ b/lib/ansible/modules/windows/win_uri.ps1
@@ -56,6 +56,17 @@ if ($use_basic_parsing) {
Add-DeprecationWarning -obj $result -message "Since Ansible 2.5, use_basic_parsing does not change any behaviour, this option will be removed" -version 2.7
}
+if ($status_code) {
+ $status_code = foreach ($code in $status_code) {
+ try {
+ [int]$code
+ }
+ catch [System.InvalidCastException] {
+ Fail-Json -obj $result -message "Failed to convert '$code' to an integer. Status codes must be provided in numeric format."
+ }
+ }
+}
+
$client = [System.Net.WebRequest]::Create($url)
$client.Method = $method
$client.Timeout = $timeout * 1000
@@ -125,7 +136,7 @@ if ($headers) {
default { $req_headers.Add($header.Name, $header.Value) }
}
}
- $client.Headers = $req_headers
+ $client.Headers.Add($req_headers)
}
if ($client_cert) {
@@ -265,7 +276,7 @@ if ($return_content -or $dest) {
}
if ($status_code -notcontains $response.StatusCode) {
- Fail-Json -obj $result -message "Status code of request '$($response.StatusCode)' is not in list of valid status codes $status_code."
+ Fail-Json -obj $result -message "Status code of request '$([int]$response.StatusCode)' is not in list of valid status codes $status_code : '$($response.StatusCode)'."
}
Exit-Json -obj $result
diff --git a/lib/ansible/parsing/vault/__init__.py b/lib/ansible/parsing/vault/__init__.py
index cca3164..7a68166 100644
--- a/lib/ansible/parsing/vault/__init__.py
+++ b/lib/ansible/parsing/vault/__init__.py
@@ -657,7 +657,7 @@ class VaultLib:
:returns: a byte string containing the decrypted data and the vault-id that was used
'''
- plaintext, vault_id = self.decrypt_and_get_vault_id(vaulttext, filename=filename)
+ plaintext, vault_id, vault_secret = self.decrypt_and_get_vault_id(vaulttext, filename=filename)
return plaintext
def decrypt_and_get_vault_id(self, vaulttext, filename=None):
@@ -668,7 +668,7 @@ class VaultLib:
:kwarg filename: a filename that the data came from. This is only
used to make better error messages in case the data cannot be
decrypted.
- :returns: a byte string containing the decrypted data and the vault-id that was used
+ :returns: a byte string containing the decrypted data and the vault-id vault-secret that was used
"""
b_vaulttext = to_bytes(vaulttext, errors='strict', encoding='utf-8')
@@ -709,6 +709,7 @@ class VaultLib:
vault_id_matchers = []
vault_id_used = None
+ vault_secret_used = None
if vault_id:
display.vvvvv('Found a vault_id (%s) in the vaulttext' % (vault_id))
@@ -737,6 +738,7 @@ class VaultLib:
b_plaintext = this_cipher.decrypt(b_vaulttext, vault_secret)
if b_plaintext is not None:
vault_id_used = vault_secret_id
+ vault_secret_used = vault_secret
file_slug = ''
if filename:
file_slug = ' of "%s"' % filename
@@ -765,7 +767,7 @@ class VaultLib:
msg += " on %s" % to_native(filename)
raise AnsibleError(msg)
- return b_plaintext, vault_id_used
+ return b_plaintext, vault_id_used, vault_secret_used
class VaultEditor:
@@ -931,7 +933,8 @@ class VaultEditor:
self._edit_file_helper(filename, secret, vault_id=vault_id)
def edit_file(self, filename):
-
+ vault_id_used = None
+ vault_secret_used = None
# follow the symlink
filename = self._real_path(filename)
@@ -943,7 +946,7 @@ class VaultEditor:
try:
# vaulttext gets converted back to bytes, but alas
# TODO: return the vault_id that worked?
- plaintext, vault_id_used = self.vault.decrypt_and_get_vault_id(vaulttext)
+ plaintext, vault_id_used, vault_secret_used = self.vault.decrypt_and_get_vault_id(vaulttext)
except AnsibleError as e:
raise AnsibleError("%s for %s" % (to_bytes(e), to_bytes(filename)))
@@ -956,21 +959,14 @@ class VaultEditor:
# as when the edited file has no vault-id but is decrypted by non-default id in secrets
# (vault_id=default, while a different vault-id decrypted)
- # if we could decrypt, the vault_id should be in secrets or we use vault_id_used
- # though we could have multiple secrets for a given vault_id, pick the first one
- secrets = match_secrets(self.vault.secrets, [vault_id_used, vault_id])
-
- if not secrets:
- raise AnsibleVaultError('Attempting to encrypt "%s" but no vault secrets were found for vault ids "%s" or "%s"' %
- (filename, vault_id, vault_id_used))
-
- secret = secrets[0][1]
-
+ # Keep the same vault-id (and version) as in the header
if cipher_name not in CIPHER_WRITE_WHITELIST:
# we want to get rid of files encrypted with the AES cipher
- self._edit_file_helper(filename, secret, existing_data=plaintext, force_save=True, vault_id=vault_id)
+ self._edit_file_helper(filename, vault_secret_used, existing_data=plaintext,
+ force_save=True, vault_id=vault_id)
else:
- self._edit_file_helper(filename, secret, existing_data=plaintext, force_save=False, vault_id=vault_id)
+ self._edit_file_helper(filename, vault_secret_used, existing_data=plaintext,
+ force_save=False, vault_id=vault_id)
def plaintext(self, filename):
@@ -996,7 +992,7 @@ class VaultEditor:
display.vvvvv('Rekeying file "%s" to with new vault-id "%s" and vault secret %s' %
(filename, new_vault_id, new_vault_secret))
try:
- plaintext, vault_id_used = self.vault.decrypt_and_get_vault_id(vaulttext)
+ plaintext, vault_id_used, _dummy = self.vault.decrypt_and_get_vault_id(vaulttext)
except AnsibleError as e:
raise AnsibleError("%s for %s" % (to_bytes(e), to_bytes(filename)))
diff --git a/lib/ansible/playbook/conditional.py b/lib/ansible/playbook/conditional.py
index 33ee54c..90c213b 100644
--- a/lib/ansible/playbook/conditional.py
+++ b/lib/ansible/playbook/conditional.py
@@ -176,7 +176,7 @@ class Conditional:
)
try:
e = templar.environment.overlay()
- e.filters.update(templar._get_filters())
+ e.filters.update(templar._get_filters(e.filters))
e.tests.update(templar._get_tests())
res = e._parse(conditional, None, None)
diff --git a/lib/ansible/playbook/included_file.py b/lib/ansible/playbook/included_file.py
index 7d9bd4e..9945244 100644
--- a/lib/ansible/playbook/included_file.py
+++ b/lib/ansible/playbook/included_file.py
@@ -146,13 +146,14 @@ class IncludedFile:
if role_name is not None:
role_name = templar.template(role_name)
- original_task._role_name = role_name
- for from_arg in original_task.FROM_ARGS:
+ new_task = original_task.copy()
+ new_task._role_name = role_name
+ for from_arg in new_task.FROM_ARGS:
if from_arg in include_variables:
from_key = from_arg.replace('_from', '')
- original_task._from_files[from_key] = templar.template(include_variables[from_arg])
+ new_task._from_files[from_key] = templar.template(include_variables[from_arg])
- inc_file = IncludedFile("role", include_variables, original_task, is_role=True)
+ inc_file = IncludedFile("role", include_variables, new_task, is_role=True)
try:
pos = included_files.index(inc_file)
diff --git a/lib/ansible/playbook/role_include.py b/lib/ansible/playbook/role_include.py
index 9d32f43..423fcdd 100644
--- a/lib/ansible/playbook/role_include.py
+++ b/lib/ansible/playbook/role_include.py
@@ -112,12 +112,12 @@ class IncludeRole(TaskInclude):
# name is needed, or use role as alias
ir._role_name = ir.args.get('name', ir.args.get('role'))
if ir._role_name is None:
- raise AnsibleParserError("'name' is a required field for include_role.")
+ raise AnsibleParserError("'name' is a required field for %s." % ir.action)
# validate bad args, otherwise we silently ignore
bad_opts = my_arg_names.difference(IncludeRole.VALID_ARGS)
if bad_opts:
- raise AnsibleParserError('Invalid options for include_role: %s' % ','.join(list(bad_opts)))
+ raise AnsibleParserError('Invalid options for %s: %s' % (ir.action, ','.join(list(bad_opts))))
# build options for role includes
for key in my_arg_names.intersection(IncludeRole.FROM_ARGS):
diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py
index 47bfd49..226380c 100644
--- a/lib/ansible/playbook/task.py
+++ b/lib/ansible/playbook/task.py
@@ -184,6 +184,11 @@ class Task(Base, Conditional, Taggable, Become):
try:
(action, args, delegate_to) = args_parser.parse()
except AnsibleParserError as e:
+ # if the raises exception was created with obj=ds args, then it includes the detail
+ # so we dont need to add it so we can just re raise.
+ if e._obj:
+ raise
+ # But if it wasn't, we can add the yaml object now to get more detail
raise AnsibleParserError(to_native(e), obj=ds, orig_exc=e)
# the command/shell/script modules used to support the `cmd` arg,
diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py
index 0e03713..eb117f6 100644
--- a/lib/ansible/plugins/action/__init__.py
+++ b/lib/ansible/plugins/action/__init__.py
@@ -153,8 +153,8 @@ class ActionBase(with_metaclass(ABCMeta, object)):
final_environment = dict()
self._compute_environment_string(final_environment)
- (module_data, module_style, module_shebang) = modify_module(module_name, module_path, module_args,
- task_vars=task_vars, templar=self._templar,
+ (module_data, module_style, module_shebang) = modify_module(module_name, module_path, module_args, self._templar,
+ task_vars=task_vars,
module_compression=self._play_context.module_compression,
async_timeout=self._task.async_val,
become=self._play_context.become,
diff --git a/lib/ansible/plugins/action/edgeos_config.py b/lib/ansible/plugins/action/edgeos_config.py
new file mode 100644
index 0000000..8fa91ea
--- /dev/null
+++ b/lib/ansible/plugins/action/edgeos_config.py
@@ -0,0 +1,113 @@
+#
+# Copyright 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 <http://www.gnu.org/licenses/>.
+#
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import os
+import re
+import time
+import glob
+
+from ansible.plugins.action.normal import ActionModule as _ActionModule
+from ansible.module_utils._text import to_text
+from ansible.module_utils.six.moves.urllib.parse import urlsplit
+
+
+PRIVATE_KEYS_RE = re.compile('__.+__')
+
+
+class ActionModule(_ActionModule):
+
+ def run(self, tmp=None, task_vars=None):
+
+ if self._task.args.get('src'):
+ try:
+ self._handle_template()
+ except ValueError as exc:
+ return dict(failed=True, msg=to_text(exc))
+
+ result = super(ActionModule, self).run(tmp, task_vars)
+ del tmp # tmp no longer has any effect
+
+ if self._task.args.get('backup') and result.get('__backup__'):
+ # User requested backup and no error occurred in module.
+ # NOTE: If there is a parameter error, _backup key may not be in results.
+ filepath = self._write_backup(task_vars['inventory_hostname'],
+ result['__backup__'])
+
+ result['backup_path'] = filepath
+
+ # strip out any keys that have two leading and two trailing
+ # underscore characters
+ for key in list(result.keys()):
+ if PRIVATE_KEYS_RE.match(key):
+ del result[key]
+
+ return result
+
+ def _get_working_path(self):
+ cwd = self._loader.get_basedir()
+ if self._task._role is not None:
+ cwd = self._task._role._role_path
+ return cwd
+
+ def _write_backup(self, host, contents):
+ backup_path = self._get_working_path() + '/backup'
+ if not os.path.exists(backup_path):
+ os.mkdir(backup_path)
+ for fn in glob.glob('%s/%s*' % (backup_path, host)):
+ os.remove(fn)
+ tstamp = time.strftime("%Y-%m-%d@%H:%M:%S", time.localtime(time.time()))
+ filename = '%s/%s_config.%s' % (backup_path, host, tstamp)
+ open(filename, 'w').write(contents)
+ return filename
+
+ def _handle_template(self):
+ src = self._task.args.get('src')
+ working_path = self._get_working_path()
+
+ if os.path.isabs(src) or urlsplit('src').scheme:
+ source = src
+ else:
+ source = self._loader.path_dwim_relative(working_path, 'templates', src)
+ if not source:
+ source = self._loader.path_dwim_relative(working_path, src)
+
+ if not os.path.exists(source):
+ raise ValueError('path specified in src not found')
+
+ try:
+ with open(source, 'r') as f:
+ template_data = to_text(f.read())
+ except IOError:
+ return dict(failed=True, msg='unable to load src file')
+
+ # Create a template search path in the following order:
+ # [working_path, self_role_path, dependent_role_paths, dirname(source)]
+ searchpath = [working_path]
+ if self._task._role is not None:
+ searchpath.append(self._task._role._role_path)
+ if hasattr(self._task, "_block:"):
+ dep_chain = self._task._block.get_dep_chain()
+ if dep_chain is not None:
+ for role in dep_chain:
+ searchpath.append(role._role_path)
+ searchpath.append(os.path.dirname(source))
+ self._templar.environment.loader.searchpath = searchpath
+ self._task.args['src'] = self._templar.template(template_data)
diff --git a/lib/ansible/plugins/action/include_vars.py b/lib/ansible/plugins/action/include_vars.py
index 7ef24e9..8897f87 100644
--- a/lib/ansible/plugins/action/include_vars.py
+++ b/lib/ansible/plugins/action/include_vars.py
@@ -236,7 +236,7 @@ class ActionModule(ActionBase):
data = to_text(b_data, errors='surrogate_or_strict')
self.show_content = show_content
- data = self._loader.load(data, show_content)
+ data = self._loader.load(data, file_name=filename, show_content=show_content)
if not data:
data = dict()
if not isinstance(data, dict):
diff --git a/lib/ansible/plugins/action/junos.py b/lib/ansible/plugins/action/junos.py
index dcba154..64913c1 100644
--- a/lib/ansible/plugins/action/junos.py
+++ b/lib/ansible/plugins/action/junos.py
@@ -37,6 +37,8 @@ except ImportError:
from ansible.utils.display import Display
display = Display()
+CLI_SUPPORTED_MODULES = ['junos_netconf', 'junos_command']
+
class ActionModule(_ActionModule):
@@ -54,6 +56,12 @@ class ActionModule(_ActionModule):
pc = copy.deepcopy(self._play_context)
pc.network_os = 'junos'
pc.remote_addr = provider['host'] or self._play_context.remote_addr
+
+ if provider['transport'] == 'cli' and self._task.action not in CLI_SUPPORTED_MODULES:
+ return {'failed': True, 'msg': "Transport type '%s' is not valid for '%s' module. "
+ "Please see http://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html"
+ % (provider['transport'], self._task.action)}
+
if self._task.action == 'junos_netconf' or (provider['transport'] == 'cli' and self._task.action == 'junos_command'):
pc.connection = 'network_cli'
pc.port = int(provider['port'] or self._play_context.port or 22)
@@ -81,8 +89,12 @@ class ActionModule(_ActionModule):
provider = self._task.args.get('provider', {})
if any(provider.values()):
display.warning('provider is unnecessary when using connection=%s and will be ignored' % self._play_context.connection)
- else:
- return {'failed': True, 'msg': 'Connection type %s is not valid for this module' % self._play_context.connection}
+
+ if (self._play_context.connection == 'network_cli' and self._task.action not in CLI_SUPPORTED_MODULES) or \
+ (self._play_context.connection == 'netconf' and self._task.action == 'junos_netconf'):
+ return {'failed': True, 'msg': "Connection type '%s' is not valid for '%s' module. "
+ "Please see http://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html"
+ % (self._play_context.connection, self._task.action)}
if (self._play_context.connection == 'local' and pc.connection == 'network_cli') or self._play_context.connection == 'network_cli':
# make sure we are in the right cli context which should be
diff --git a/lib/ansible/plugins/action/onyx_config.py b/lib/ansible/plugins/action/onyx_config.py
index 3b84e2a..a290d5c 100644
--- a/lib/ansible/plugins/action/onyx_config.py
+++ b/lib/ansible/plugins/action/onyx_config.py
@@ -54,7 +54,7 @@ class ActionModule(_ActionModule):
# strip out any keys that have two leading and two trailing
# underscore characters
- for key in result.keys():
+ for key in list(result.keys()):
if PRIVATE_KEYS_RE.match(key):
del result[key]
diff --git a/lib/ansible/plugins/action/vyos_config.py b/lib/ansible/plugins/action/vyos_config.py
index 2c780b1..618e26b 100644
--- a/lib/ansible/plugins/action/vyos_config.py
+++ b/lib/ansible/plugins/action/vyos_config.py
@@ -41,7 +41,7 @@ class ActionModule(_ActionModule):
try:
self._handle_template()
except ValueError as exc:
- return dict(failed=True, msg=exc.message)
+ return dict(failed=True, msg=to_text(exc))
result = super(ActionModule, self).run(tmp, task_vars)
del tmp # tmp no longer has any effect
@@ -56,7 +56,7 @@ class ActionModule(_ActionModule):
# strip out any keys that have two leading and two trailing
# underscore characters
- for key in result.keys():
+ for key in list(result.keys()):
if PRIVATE_KEYS_RE.match(key):
del result[key]
diff --git a/lib/ansible/plugins/action/wait_for_connection.py b/lib/ansible/plugins/action/wait_for_connection.py
index 1f777e8..4547368 100644
--- a/lib/ansible/plugins/action/wait_for_connection.py
+++ b/lib/ansible/plugins/action/wait_for_connection.py
@@ -54,11 +54,12 @@ class ActionModule(ActionBase):
display.debug("wait_for_connection: %s success" % what_desc)
return
except Exception as e:
+ error = e # PY3 compatibility to store exception for use outside of this block
if what_desc:
display.debug("wait_for_connection: %s fail (expected), retrying in %d seconds..." % (what_desc, sleep))
time.sleep(sleep)
- raise TimedOutException("timed out waiting for %s: %s" % (what_desc, e))
+ raise TimedOutException("timed out waiting for %s: %s" % (what_desc, error))
def run(self, tmp=None, task_vars=None):
if task_vars is None:
diff --git a/lib/ansible/plugins/action/win_copy.py b/lib/ansible/plugins/action/win_copy.py
index 3b4616c..0566b95 100644
--- a/lib/ansible/plugins/action/win_copy.py
+++ b/lib/ansible/plugins/action/win_copy.py
@@ -11,6 +11,7 @@ import base64
import json
import os
import os.path
+import shutil
import tempfile
import traceback
import zipfile
@@ -320,12 +321,10 @@ class ActionModule(ActionBase):
)
)
copy_args.pop('content', None)
- os.remove(zip_path)
-
module_return = self._execute_module(module_name='copy',
module_args=copy_args,
task_vars=task_vars)
- os.removedirs(os.path.dirname(zip_path))
+ shutil.rmtree(os.path.dirname(zip_path))
return module_return
def run(self, tmp=None, task_vars=None):
diff --git a/lib/ansible/plugins/action/win_reboot.py b/lib/ansible/plugins/action/win_reboot.py
index eccce67..015c15e 100644
--- a/lib/ansible/plugins/action/win_reboot.py
+++ b/lib/ansible/plugins/action/win_reboot.py
@@ -85,7 +85,7 @@ class ActionModule(ActionBase):
}
for arg, version in deprecated_args.items():
if self._task.args.get(arg) is not None:
- display.warning("Since Ansible %s, %s is no longer used with win_reboot" % (arg, version))
+ display.warning("Since Ansible %s, %s is no longer used with win_reboot" % (version, arg))
if self._task.args.get('connect_timeout') is not None:
connect_timeout = int(self._task.args.get('connect_timeout', self.DEFAULT_CONNECT_TIMEOUT))
diff --git a/lib/ansible/plugins/action/win_updates.py b/lib/ansible/plugins/action/win_updates.py
index a3a0a05..766ff49 100644
--- a/lib/ansible/plugins/action/win_updates.py
+++ b/lib/ansible/plugins/action/win_updates.py
@@ -186,7 +186,15 @@ class ActionModule(ActionBase):
new_module_args.pop('reboot_timeout', None)
result = self._run_win_updates(new_module_args, task_vars)
- changed = result['changed']
+ # if the module failed to run at all then changed won't be populated
+ # so we just return the result as is
+ # https://github.com/ansible/ansible/issues/38232
+ failed = result.get('failed', False)
+ if "updates" not in result.keys() or failed:
+ result['failed'] = True
+ return result
+
+ changed = result.get('changed', False)
updates = result.get('updates', dict())
filtered_updates = result.get('filtered_updates', dict())
found_update_count = result.get('found_update_count', 0)
@@ -235,6 +243,8 @@ class ActionModule(ActionBase):
result.pop('msg', None)
# rerun the win_updates module after the reboot is complete
result = self._run_win_updates(new_module_args, task_vars)
+ if result.get('failed', False):
+ return result
result_updates = result.get('updates', dict())
result_filtered_updates = result.get('filtered_updates', dict())
diff --git a/lib/ansible/plugins/callback/foreman.py b/lib/ansible/plugins/callback/foreman.py
index 97792de..54559f9 100644
--- a/lib/ansible/plugins/callback/foreman.py
+++ b/lib/ansible/plugins/callback/foreman.py
@@ -90,9 +90,9 @@ class CallbackModule(CallbackBase):
self.items = defaultdict(list)
self.start_time = int(time.time())
- def set_options(self, options):
+ def set_options(self, task_keys=None, var_options=None, direct=None):
- super(CallbackModule, self).set_options(options)
+ super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
self.FOREMAN_URL = self._plugin_options['url']
self.FOREMAN_SSL_CERT = (self._plugin_options['ssl_cert'], self._plugin_options['ssl_key'])
diff --git a/lib/ansible/plugins/callback/oneline.py b/lib/ansible/plugins/callback/oneline.py
index 119b827..b89f8b6 100644
--- a/lib/ansible/plugins/callback/oneline.py
+++ b/lib/ansible/plugins/callback/oneline.py
@@ -56,11 +56,19 @@ class CallbackModule(CallbackBase):
color=C.COLOR_ERROR)
def v2_runner_on_ok(self, result):
+
+ if result._result.get('changed', False):
+ color = C.COLOR_CHANGED
+ state = 'CHANGED'
+ else:
+ color = C.COLOR_OK
+ state = 'SUCCESS'
+
if result._task.action in C.MODULE_NO_JSON:
- self._display.display(self._command_generic_msg(result._host.get_name(), result._result, 'SUCCESS'), color=C.COLOR_OK)
+ self._display.display(self._command_generic_msg(result._host.get_name(), result._result, state), color=color)
else:
- self._display.display("%s | SUCCESS => %s" % (result._host.get_name(), self._dump_results(result._result, indent=0).replace('\n', '')),
- color=C.COLOR_OK)
+ self._display.display("%s | %s => %s" % (result._host.get_name(), state, self._dump_results(result._result, indent=0).replace('\n', '')),
+ color=color)
def v2_runner_on_unreachable(self, result):
self._display.display("%s | UNREACHABLE!: %s" % (result._host.get_name(), result._result.get('msg', '')), color=C.COLOR_UNREACHABLE)
diff --git a/lib/ansible/plugins/callback/slack.py b/lib/ansible/plugins/callback/slack.py
index aac9062..4638c1b 100644
--- a/lib/ansible/plugins/callback/slack.py
+++ b/lib/ansible/plugins/callback/slack.py
@@ -152,7 +152,7 @@ class CallbackModule(CallbackBase):
invocation_items.append('Extra Vars: %s' %
' '.join(extra_vars))
- title.append('by *%s*' % self.get_options('remote_user'))
+ title.append('by *%s*' % self.get_option('remote_user'))
title.append('\n\n*%s*' % self.playbook_name)
msg_items = [' '.join(title)]
diff --git a/lib/ansible/plugins/callback/yaml.py b/lib/ansible/plugins/callback/yaml.py
index e8a23ea..971193a 100644
--- a/lib/ansible/plugins/callback/yaml.py
+++ b/lib/ansible/plugins/callback/yaml.py
@@ -47,7 +47,7 @@ def my_represent_scalar(self, tag, value, style=None):
# ...no trailing space
value = value.rstrip()
# ...and non-printable characters
- value = filter(lambda x: x in string.printable, value)
+ value = ''.join(x for x in value if x in string.printable)
# ...tabs prevent blocks from expanding
value = value.expandtabs()
# ...and odd bits of whitespace
diff --git a/lib/ansible/plugins/cliconf/eos.py b/lib/ansible/plugins/cliconf/eos.py
index b6fda0c..bc21dec 100644
--- a/lib/ansible/plugins/cliconf/eos.py
+++ b/lib/ansible/plugins/cliconf/eos.py
@@ -51,13 +51,12 @@ class Cliconf(CliconfBase):
lookup = {'running': 'running-config', 'startup': 'startup-config'}
if source not in lookup:
return self.invalid_params("fetching configuration from %s is not supported" % source)
- if format == 'text':
- cmd = b'show %s ' % lookup[source]
- else:
- cmd = b'show %s | %s' % (lookup[source], format)
- flags = [] if flags is None else flags
- cmd += ' '.join(flags)
+ cmd = b'show %s ' % lookup[source]
+ if format and format is not 'text':
+ cmd += b'| %s ' % format
+
+ cmd += ' '.join(to_list(flags))
cmd = cmd.strip()
return self.send_command(cmd)
diff --git a/lib/ansible/plugins/cliconf/junos.py b/lib/ansible/plugins/cliconf/junos.py
index 32fd19a..2f0cb92 100644
--- a/lib/ansible/plugins/cliconf/junos.py
+++ b/lib/ansible/plugins/cliconf/junos.py
@@ -84,10 +84,8 @@ class Cliconf(CliconfBase):
command += b' and-quit'
return self.send_command(command)
- def discard_changes(self, rollback_id=None):
- command = b'rollback'
- if rollback_id is not None:
- command += b' %s' % int(rollback_id)
+ def discard_changes(self):
+ command = b'rollback 0'
for cmd in chain(to_list(command), b'exit'):
self.send_command(cmd)
diff --git a/lib/ansible/plugins/connection/netconf.py b/lib/ansible/plugins/connection/netconf.py
index 46b120c..29aa2d2 100644
--- a/lib/ansible/plugins/connection/netconf.py
+++ b/lib/ansible/plugins/connection/netconf.py
@@ -67,7 +67,8 @@ options:
- Configures the user password used to authenticate to the remote device
when first establishing the SSH connection.
vars:
- - name: ansible_pass
+ - name: ansible_password
+ - name: ansible_ssh_pass
private_key_file:
description:
- The private SSH key or certificate file used to to authenticate to the
@@ -157,8 +158,9 @@ except ImportError:
logging.getLogger('ncclient').setLevel(logging.INFO)
-network_os_device_param_map = {
- "nxos": "nexus"
+NETWORK_OS_DEVICE_PARAM_MAP = {
+ "nxos": "nexus",
+ "ios": "default"
}
@@ -242,7 +244,7 @@ class Connection(ConnectionBase):
if network_os:
display.display('discovered network_os %s' % network_os, log_only=True)
- device_params = {'name': (network_os_device_param_map.get(network_os) or network_os or 'default')}
+ device_params = {'name': (NETWORK_OS_DEVICE_PARAM_MAP.get(network_os) or network_os or 'default')}
ssh_config = os.getenv('ANSIBLE_NETCONF_SSH_CONFIG', False)
if ssh_config in BOOLEANS_TRUE:
diff --git a/lib/ansible/plugins/connection/network_cli.py b/lib/ansible/plugins/connection/network_cli.py
index fedbada..e5568d6 100644
--- a/lib/ansible/plugins/connection/network_cli.py
+++ b/lib/ansible/plugins/connection/network_cli.py
@@ -62,7 +62,8 @@ options:
- Configures the user password used to authenticate to the remote device
when first establishing the SSH connection.
vars:
- - name: ansible_pass
+ - name: ansible_password
+ - name: ansible_ssh_pass
private_key_file:
description:
- The private SSH key or certificate file used to to authenticate to the
@@ -369,8 +370,11 @@ class Connection(ConnectionBase):
self._ssh_shell.close()
self._ssh_shell = None
display.debug("cli session is now closed")
+
+ self.paramiko_conn.close()
+ self.paramiko_conn = None
+ display.debug("ssh connection has been closed successfully")
self._connected = False
- display.debug("ssh connection has been closed successfully")
def receive(self, command=None, prompts=None, answer=None, newline=True, prompt_retry_check=False):
'''
diff --git a/lib/ansible/plugins/connection/persistent.py b/lib/ansible/plugins/connection/persistent.py
index 16faf1a..ac2406f 100644
--- a/lib/ansible/plugins/connection/persistent.py
+++ b/lib/ansible/plugins/connection/persistent.py
@@ -78,9 +78,21 @@ class Connection(ConnectionBase):
master, slave = pty.openpty()
python = sys.executable
- # Assume ansible-connection is in the same dir as sys.argv[0]
- ansible_connection = os.path.join(os.path.dirname(sys.argv[0]), 'ansible-connection')
- p = subprocess.Popen([python, ansible_connection, to_text(os.getppid())], stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ def find_file_in_path(filename):
+ # Check $PATH first, followed by same directory as sys.argv[0]
+ paths = os.environ['PATH'].split(os.pathsep) + [os.path.dirname(sys.argv[0])]
+ for dirname in paths:
+ fullpath = os.path.join(dirname, filename)
+ if os.path.isfile(fullpath):
+ return fullpath
+
+ raise AnsibleError("Unable to find location of '%s'" % filename)
+
+ p = subprocess.Popen(
+ [python, find_file_in_path('ansible-connection'), to_text(os.getppid())],
+ stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
stdin = os.fdopen(master, 'wb', 0)
os.close(slave)
@@ -103,7 +115,8 @@ class Connection(ConnectionBase):
else:
try:
result = json.loads(to_text(stderr, errors='surrogate_then_replace'))
- except json.decoder.JSONDecodeError:
+ except getattr(json.decoder, 'JSONDecodeError', ValueError):
+ # JSONDecodeError only available on Python 3.5+
result = {'error': to_text(stderr, errors='surrogate_then_replace')}
if 'messages' in result:
diff --git a/lib/ansible/plugins/connection/winrm.py b/lib/ansible/plugins/connection/winrm.py
index 499dbf5..5d378dc 100644
--- a/lib/ansible/plugins/connection/winrm.py
+++ b/lib/ansible/plugins/connection/winrm.py
@@ -115,6 +115,7 @@ except ImportError:
from ansible.errors import AnsibleError, AnsibleConnectionFailure
from ansible.errors import AnsibleFileNotFound
+from ansible.module_utils.parsing.convert_bool import boolean
from ansible.module_utils.six.moves.urllib.parse import urlunsplit
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.six import binary_type
@@ -269,12 +270,22 @@ class Connection(ConnectionBase):
os.environ["KRB5CCNAME"] = krb5ccname
krb5env = dict(KRB5CCNAME=krb5ccname)
+ # stores various flags to call with kinit, we currently only use this
+ # to set -f so we can get a forward-able ticket (cred delegation)
+ kinit_flags = []
+ if boolean(self.get_option('_extras').get('ansible_winrm_kerberos_delegation', False)):
+ kinit_flags.append('-f')
+
+ kinit_cmdline = [self._kinit_cmd]
+ kinit_cmdline.extend(kinit_flags)
+ kinit_cmdline.append(principal)
+
# pexpect runs the process in its own pty so it can correctly send
# the password as input even on MacOS which blocks subprocess from
# 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 = "%s %s" % (self._kinit_cmd, principal)
+ kinit_cmdline = " ".join(kinit_cmdline)
password = to_text(password, encoding='utf-8',
errors='surrogate_or_strict')
@@ -283,11 +294,10 @@ class Connection(ConnectionBase):
events = {
".*:": password + "\n"
}
- # technically this is the stdout but to match subprocess we wil call
- # it stderr
+ # 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)
else:
- kinit_cmdline = [self._kinit_cmd, principal]
password = to_bytes(password, encoding='utf-8',
errors='surrogate_or_strict')
diff --git a/lib/ansible/plugins/inventory/__init__.py b/lib/ansible/plugins/inventory/__init__.py
index 33288dc..1488b99 100644
--- a/lib/ansible/plugins/inventory/__init__.py
+++ b/lib/ansible/plugins/inventory/__init__.py
@@ -24,9 +24,9 @@ import os
import re
import string
-from collections import MutableMapping
+from collections import Mapping
-from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
+from ansible.errors import AnsibleError, AnsibleParserError
from ansible.plugins import AnsiblePlugin
from ansible.plugins.cache import InventoryFileCacheModule
from ansible.module_utils._text import to_bytes, to_native
@@ -159,7 +159,7 @@ class BaseInventoryPlugin(AnsiblePlugin):
return (os.path.exists(b_path) and os.access(b_path, os.R_OK))
def _populate_host_vars(self, hosts, variables, group=None, port=None):
- if not isinstance(variables, MutableMapping):
+ if not isinstance(variables, Mapping):
raise AnsibleParserError("Invalid data from file, expected dictionary and got:\n\n%s" % to_native(variables))
for host in hosts:
@@ -172,7 +172,9 @@ class BaseInventoryPlugin(AnsiblePlugin):
config = {}
try:
- config = self.loader.load_from_file(path)
+ # avoid loader cache so meta: refresh_inventory can pick up config changes
+ # if we read more than once, fs cache should be good enough
+ config = self.loader.load_from_file(path, cache=False)
except Exception as e:
raise AnsibleParserError(to_native(e))
@@ -182,7 +184,7 @@ class BaseInventoryPlugin(AnsiblePlugin):
elif config.get('plugin') != self.NAME:
# this is not my config file
raise AnsibleParserError("Incorrect plugin name in file: %s" % config.get('plugin', 'none found'))
- elif not isinstance(config, MutableMapping):
+ elif not isinstance(config, Mapping):
# configs are dictionaries
raise AnsibleParserError('inventory source has invalid structure, it should be a dictionary, got: %s' % type(config))
@@ -253,7 +255,7 @@ class Constructable(object):
''' helper method for pluigns to compose variables for Ansible based on jinja2 expression and inventory vars'''
t = self.templar
t.set_available_variables(variables)
- return t.do_template('%s%s%s' % (t.environment.variable_start_string, template, t.environment.variable_end_string), disable_lookups=True)
+ return t.template('%s%s%s' % (t.environment.variable_start_string, template, t.environment.variable_end_string), disable_lookups=True)
def _set_composite_vars(self, compose, variables, host, strict=False):
''' loops over compose entries to create vars for hosts '''
@@ -263,7 +265,7 @@ class Constructable(object):
composite = self._compose(compose[varname], variables)
except Exception as e:
if strict:
- raise AnsibleOptionsError("Could set %s: %s" % (varname, to_native(e)))
+ raise AnsibleError("Could not set %s: %s" % (varname, to_native(e)))
continue
self.inventory.set_variable(host, varname, composite)
@@ -278,8 +280,9 @@ class Constructable(object):
result = boolean(self.templar.template(conditional))
except Exception as e:
if strict:
- raise AnsibleOptionsError("Could not add to group %s: %s" % (group_name, to_native(e)))
+ raise AnsibleParserError("Could not add host %s to group %s: %s" % (host, group_name, to_native(e)))
continue
+
if result:
# ensure group exists
self.inventory.add_group(group_name)
@@ -289,27 +292,40 @@ class Constructable(object):
def _add_host_to_keyed_groups(self, keys, variables, host, strict=False):
''' helper to create groups for plugins based on variable values and add the corresponding hosts to it'''
if keys and isinstance(keys, list):
+ groups = []
for keyed in keys:
if keyed and isinstance(keyed, dict):
- prefix = keyed.get('prefix', '')
- key = keyed.get('key')
- if key is not None:
- try:
- groups = to_safe_group_name('%s_%s' % (prefix, self._compose(key, variables)))
- except Exception as e:
- if strict:
- raise AnsibleOptionsError("Could not generate group on %s: %s" % (key, to_native(e)))
- continue
- if isinstance(groups, string_types):
- groups = [groups]
- if isinstance(groups, list):
- for group_name in groups:
- if group_name not in self.inventory.groups:
- self.inventory.add_group(group_name)
- self.inventory.add_child(group_name, host)
+
+ try:
+ key = self._compose(keyed.get('key'), variables)
+ except Exception as e:
+ if strict:
+ raise AnsibleParserError("Could not generate group from %s entry: %s" % (keyed.get('key'), to_native(e)))
+ continue
+
+ if key:
+ prefix = keyed.get('prefix', '')
+ sep = keyed.get('separator', '_')
+
+ if isinstance(key, string_types):
+ groups.append('%s%s%s' % (prefix, sep, key))
+ elif isinstance(key, list):
+ for name in key:
+ groups.append('%s%s%s' % (prefix, sep, name))
+ elif isinstance(key, Mapping):
+ for (gname, gval) in key.items():
+ name = '%s%s%s' % (gname, sep, gval)
+ groups.append('%s%s%s' % (prefix, sep, name))
else:
- raise AnsibleOptionsError("Invalid group name format, expected string or list of strings, got: %s" % type(groups))
+ raise AnsibleParserError("Invalid group name format, expected a string or a list of them or dictionary, got: %s" % type(key))
else:
- raise AnsibleOptionsError("No key supplied, invalid entry")
+ if strict:
+ raise AnsibleParserError("No key or key resulted empty, invalid entry")
else:
- raise AnsibleOptionsError("Invalid keyed group entry, it must be a dictionary: %s " % keyed)
+ raise AnsibleParserError("Invalid keyed group entry, it must be a dictionary: %s " % keyed)
+
+ # now actually add any groups
+ for group_name in groups:
+ gname = to_safe_group_name(group_name)
+ self.inventory.add_group(gname)
+ self.inventory.add_child(gname, host)
diff --git a/lib/ansible/plugins/inventory/auto.py b/lib/ansible/plugins/inventory/auto.py
index c2bd7f8..62502c3 100644
--- a/lib/ansible/plugins/inventory/auto.py
+++ b/lib/ansible/plugins/inventory/auto.py
@@ -38,7 +38,7 @@ class InventoryModule(BaseInventoryPlugin):
return super(InventoryModule, self).verify_file(path)
def parse(self, inventory, loader, path, cache=True):
- config_data = loader.load_from_file(path)
+ config_data = loader.load_from_file(path, cache=False)
plugin_name = config_data.get('plugin')
diff --git a/lib/ansible/plugins/inventory/constructed.py b/lib/ansible/plugins/inventory/constructed.py
index a6ace76..4f1ccf5 100644
--- a/lib/ansible/plugins/inventory/constructed.py
+++ b/lib/ansible/plugins/inventory/constructed.py
@@ -43,7 +43,8 @@ EXAMPLES = '''
multi_group: (group_names|intersection(['alpha', 'beta', 'omega']))|length >= 2
keyed_groups:
- # this creates a group per distro (distro_CentOS, distro_Debian) and assigns the hosts that have matching values to it
+ # this creates a group per distro (distro_CentOS, distro_Debian) and assigns the hosts that have matching values to it,
+ # using the default separator "_"
- prefix: distro
key: ansible_distribution
diff --git a/lib/ansible/plugins/inventory/ini.py b/lib/ansible/plugins/inventory/ini.py
index 82d4e18..93afcf1 100644
--- a/lib/ansible/plugins/inventory/ini.py
+++ b/lib/ansible/plugins/inventory/ini.py
@@ -104,8 +104,7 @@ class InventoryModule(BaseFileInventoryPlugin):
self._filename = path
try:
- # Read in the hosts, groups, and variables defined in the
- # inventory file.
+ # Read in the hosts, groups, and variables defined in the inventory file.
if self.loader:
(b_data, private) = self.loader._get_file_contents(path)
else:
diff --git a/lib/ansible/plugins/inventory/script.py b/lib/ansible/plugins/inventory/script.py
index 253c20b..b62da1f 100644
--- a/lib/ansible/plugins/inventory/script.py
+++ b/lib/ansible/plugins/inventory/script.py
@@ -19,6 +19,16 @@ DOCUMENTATION = '''
key: cache
env:
- name: ANSIBLE_INVENTORY_PLUGIN_SCRIPT_CACHE
+ always_show_stderr:
+ description: Toggle display of stderr even when script was successful
+ version_added: "2.5.1"
+ default: True
+ type: boolean
+ ini:
+ - section: inventory_plugin_script
+ key: always_show_stderr
+ env:
+ - name: ANSIBLE_INVENTORY_PLUGIN_SCRIPT_STDERR
description:
- The source provided must an executable that returns Ansible inventory JSON
- The source must accept C(--list) and C(--host <hostname>) as arguments.
@@ -64,7 +74,7 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
initial_chars = inv_file.read(2)
if initial_chars.startswith(b'#!'):
shebang_present = True
- except:
+ except Exception:
pass
if not os.access(path, os.X_OK) and not shebang_present:
@@ -99,8 +109,7 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
if sp.returncode != 0:
raise AnsibleError("Inventory script (%s) had an execution error: %s " % (path, err))
- # make sure script output is unicode so that json loader will output
- # unicode strings itself
+ # make sure script output is unicode so that json loader will output unicode strings itself
try:
data = to_text(stdout, errors="strict")
except Exception as e:
@@ -111,6 +120,10 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
except Exception as e:
raise AnsibleError("failed to parse executable inventory script results from {0}: {1}\n{2}".format(path, to_native(e), err))
+ # if no other errors happened and you want to force displaying stderr, do so now
+ if err and self.get_option('always_show_stderr'):
+ self.display.error(msg=to_text(err))
+
processed = self._cache[cache_key]
if not isinstance(processed, Mapping):
raise AnsibleError("failed to parse executable inventory script results from {0}: needs to be a json dict\n{1}".format(path, err))
diff --git a/lib/ansible/plugins/inventory/yaml.py b/lib/ansible/plugins/inventory/yaml.py
index d910917..bb05031 100644
--- a/lib/ansible/plugins/inventory/yaml.py
+++ b/lib/ansible/plugins/inventory/yaml.py
@@ -91,7 +91,7 @@ class InventoryModule(BaseFileInventoryPlugin):
super(InventoryModule, self).parse(inventory, loader, path)
try:
- data = self.loader.load_from_file(path)
+ data = self.loader.load_from_file(path, cache=False)
except Exception as e:
raise AnsibleParserError(e)
diff --git a/lib/ansible/plugins/loader.py b/lib/ansible/plugins/loader.py
index 43a6564..07b2767 100644
--- a/lib/ansible/plugins/loader.py
+++ b/lib/ansible/plugins/loader.py
@@ -211,9 +211,8 @@ class PluginLoader:
if self.class_name:
type_name = get_plugin_class(self.class_name)
- # FIXME: expand to other plugins, but never doc fragments
# if type name != 'module_doc_fragment':
- if type_name in ('callback', 'connection', 'inventory', 'lookup', 'shell'):
+ if type_name in C.CONFIGURABLE_PLUGINS:
dstring = get_docstring(path, fragment_loader, verbose=False, ignore_errors=True)[0]
if dstring and 'options' in dstring and isinstance(dstring['options'], dict):
@@ -348,9 +347,6 @@ class PluginLoader:
def _update_object(self, obj, name, path):
- # load plugin config data
- self._load_config_defs(name, path)
-
# set extra info on the module, in case we want it later
setattr(obj, '_original_path', path)
setattr(obj, '_load_name', name)
@@ -394,6 +390,10 @@ class PluginLoader:
return None
raise
+ # load plugin config data
+ if not found_in_cache:
+ self._load_config_defs(name, path)
+
self._update_object(obj, name, path)
return obj
@@ -467,6 +467,10 @@ class PluginLoader:
except TypeError as e:
display.warning("Skipping plugin (%s) as it seems to be incomplete: %s" % (path, to_text(e)))
+ # load plugin config data
+ if not found_in_cache:
+ self._load_config_defs(name, path)
+
self._update_object(obj, name, path)
yield obj
diff --git a/lib/ansible/plugins/lookup/csvfile.py b/lib/ansible/plugins/lookup/csvfile.py
index ce619fe..82e1b84 100644
--- a/lib/ansible/plugins/lookup/csvfile.py
+++ b/lib/ansible/plugins/lookup/csvfile.py
@@ -53,6 +53,7 @@ from collections import MutableSequence
from ansible.errors import AnsibleError, AnsibleAssertionError
from ansible.plugins.lookup import LookupBase
+from ansible.module_utils.six import PY2
from ansible.module_utils._text import to_bytes, to_native, to_text
@@ -66,8 +67,10 @@ class CSVRecoder:
def __iter__(self):
return self
- def next(self):
- return self.reader.next().encode("utf-8")
+ def __next__(self):
+ return next(self.reader).encode("utf-8")
+
+ next = __next__ # For Python 2
class CSVReader:
@@ -77,13 +80,19 @@ class CSVReader:
"""
def __init__(self, f, dialect=csv.excel, encoding='utf-8', **kwds):
- f = CSVRecoder(f, encoding)
+ if PY2:
+ f = CSVRecoder(f, encoding)
+ else:
+ f = codecs.getreader(encoding)(f)
+
self.reader = csv.reader(f, dialect=dialect, **kwds)
- def next(self):
- row = self.reader.next()
+ def __next__(self):
+ row = next(self.reader)
return [to_text(s) for s in row]
+ next = __next__ # For Python 2
+
def __iter__(self):
return self
@@ -93,8 +102,8 @@ class LookupModule(LookupBase):
def read_csv(self, filename, key, delimiter, encoding='utf-8', dflt=None, col=1):
try:
- f = open(filename, 'r')
- creader = CSVReader(f, delimiter=to_bytes(delimiter), encoding=encoding)
+ f = open(filename, 'rb')
+ creader = CSVReader(f, delimiter=to_native(delimiter), encoding=encoding)
for row in creader:
if row[0] == key:
diff --git a/lib/ansible/plugins/lookup/first_found.py b/lib/ansible/plugins/lookup/first_found.py
index 0b066b2..2798a0d 100644
--- a/lib/ansible/plugins/lookup/first_found.py
+++ b/lib/ansible/plugins/lookup/first_found.py
@@ -11,6 +11,10 @@ DOCUMENTATION = """
short_description: return first file found from list
description:
- this lookup checks a list of files and paths and returns the full path to the first combination found.
+ - As all lookups, when fed relative paths it will try use the current task's location first and go up the chain
+ to the containing role/play/include/etc's location.
+ - The list of files has precedence over the paths searched.
+ i.e, A task in a role has a 'file1' in the play's relative path, this will be used, 'file2' in role's relative path will not.
options:
_terms:
description: list of file names
@@ -20,38 +24,52 @@ DOCUMENTATION = """
"""
EXAMPLES = """
-- name: show first existin file
- debug: var=item
- with_first_found:
- - "/path/to/foo.txt"
- - "bar.txt" # will be looked in files/ dir relative to play or in role
- - "/path/to/biz.txt"
-
-- name: copy first existing file found to /some/file
- copy: src={{item}} dest=/some/file
- with_first_found:
- - foo
- - "{{inventory_hostname}}
- - bar
+- name: show first existing file
+ debug: msg={{lookup('first_found', findme)}}
+ vars:
+ findme:
+ - "/path/to/foo.txt"
+ - "bar.txt" # will be looked in files/ dir relative to role and/or play
+ - "/path/to/biz.txt"
+
+- name: |
+ copy first existing file found to /some/file,
+ looking in relative directories from where the task is defined and
+ including any play objects that contain it
+ copy: src={{lookup('first_found', findme)}} dest=/some/file
+ vars:
+ findme:
+ - foo
+ - "{{inventory_hostname}}
+ - bar
- name: same copy but specific paths
- copy: src={{item}} dest=/some/file
- with_first_found:
- - files:
- - foo
- - "{{inventory_hostname}}
- - bar
- paths:
- - /tmp/production
- - /tmp/staging
+ copy: src={{lookup('first_found', findme, mypaths}} dest=/some/file
+ vars:
+ findme:
+ - foo
+ - "{{inventory_hostname}}
+ - bar
+ mypaths:
+ - /tmp/production
+ - /tmp/staging
- name: INTERFACES | Create Ansible header for /etc/network/interfaces
template:
- src: "{{ item }}"
+ src: "{{ lookup('first_found', findme)}}"
dest: "/etc/foo.conf"
- with_first_found:
- - "{{ ansible_virtualization_type }}_foo.conf"
- - "default_foo.conf"
+ vars:
+ findme:
+ - "{{ ansible_virtualization_type }}_foo.conf"
+ - "default_foo.conf"
+
+- name: read vars from first file found, use 'vars/' relative subdir
+ include_vars: "{{lookup('first_found', findme, paths=['vars'])}}"
+ vars:
+ findme:
+ - '{{ansible_os_distribution}}.yml'
+ - '{{ansible_os_family}}.yml'
+ - default.yml
"""
RETURN = """
diff --git a/lib/ansible/plugins/shell/powershell.py b/lib/ansible/plugins/shell/powershell.py
index 0708abb..2b43bc9 100644
--- a/lib/ansible/plugins/shell/powershell.py
+++ b/lib/ansible/plugins/shell/powershell.py
@@ -1096,6 +1096,27 @@ $exec_wrapper = {
$DebugPreference = "Continue"
$ErrorActionPreference = "Stop"
+ # become process is run under a different console to the WinRM one so we
+ # need to set the UTF-8 codepage again
+ Add-Type -Debug:$false -TypeDefinition @'
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ansible
+{
+ public class ConsoleCP
+ {
+ [DllImport("kernel32.dll")]
+ public static extern bool SetConsoleCP(UInt32 wCodePageID);
+
+ [DllImport("kernel32.dll")]
+ public static extern bool SetConsoleOutputCP(UInt32 wCodePageID);
+ }
+}
+'@
+ [Ansible.ConsoleCP]::SetConsoleCP(65001) > $null
+ [Ansible.ConsoleCP]::SetConsoleOutputCP(65001) > $null
+
Function ConvertTo-HashtableFromPsCustomObject($myPsObject) {
$output = @{}
$myPsObject | Get-Member -MemberType *Property | % {
@@ -1142,8 +1163,8 @@ $exec_wrapper = {
}
$output = $entrypoint.Run($payload)
-
- Write-Output $output
+ # base64 encode the output so the non-ascii characters are preserved
+ Write-Output ([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((Write-Output $output))))
} # end exec_wrapper
Function Dump-Error ($excep) {
@@ -1262,10 +1283,11 @@ Function Run($payload) {
$result = [Ansible.BecomeUtil]::RunAsUser($username, $password, $lp_command_line, $lp_current_directory, $payload_string, $logon_flags, $logon_type)
$stdout = $result.StandardOut
+ $stdout = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($stdout.Trim()))
$stderr = $result.StandardError
$rc = $result.ExitCode
- [Console]::Out.WriteLine($stdout.Trim())
+ [Console]::Out.WriteLine($stdout)
[Console]::Error.WriteLine($stderr.Trim())
} Catch {
$excep = $_
diff --git a/lib/ansible/release.py b/lib/ansible/release.py
index e19d40e..79c5436 100644
--- a/lib/ansible/release.py
+++ b/lib/ansible/release.py
@@ -19,5 +19,5 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-__version__ = '2.5.0'
+__version__ = '2.5.1'
__author__ = 'Ansible, Inc.'
diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py
index 4cd9ee8..aaccc3a 100644
--- a/lib/ansible/template/__init__.py
+++ b/lib/ansible/template/__init__.py
@@ -292,7 +292,7 @@ class Templar:
))
self._no_type_regex = re.compile(r'.*\|\s*(?:%s)\s*(?:%s)?$' % ('|'.join(C.STRING_TYPE_FILTERS), self.environment.variable_end_string))
- def _get_filters(self):
+ def _get_filters(self, builtin_filters):
'''
Returns filter plugins, after loading and caching them if need be
'''
@@ -308,6 +308,9 @@ class Templar:
# TODO: Remove registering tests as filters in 2.9
for name, func in self._get_tests().items():
+ if name in builtin_filters:
+ # If we have a custom test named the same as a builtin filter, don't register as a filter
+ continue
self._filters[name] = tests_as_filters_warning(name, func)
return self._filters.copy()
@@ -681,7 +684,7 @@ class Templar:
setattr(myenv, key, ast.literal_eval(val.strip()))
# Adds Ansible custom filters and tests
- myenv.filters.update(self._get_filters())
+ myenv.filters.update(self._get_filters(myenv.filters))
myenv.tests.update(self._get_tests())
if escape_backslashes:
diff --git a/lib/ansible/utils/module_docs_fragments/vmware.py b/lib/ansible/utils/module_docs_fragments/vmware.py
index 2752285..febab2e 100644
--- a/lib/ansible/utils/module_docs_fragments/vmware.py
+++ b/lib/ansible/utils/module_docs_fragments/vmware.py
@@ -1,19 +1,6 @@
-# (c) 2016, Charles Paul <cpaul@ansible.com>
-#
-# 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 <http://www.gnu.org/licenses/>.
+# Copyright: (c) 2016, Charles Paul <cpaul@ansible.com>
+# Copyright: (c) 2018, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
class ModuleDocFragment(object):
@@ -21,29 +8,38 @@ class ModuleDocFragment(object):
DOCUMENTATION = '''
options:
hostname:
- description:
- - The hostname or IP address of the vSphere vCenter.
- required: True
+ description:
+ - The hostname or IP address of the vSphere vCenter or ESXi server.
+ - If the value is not specified in the task, the value of environment variable C(VMWARE_HOST) will be used instead.
+ - Environment variable supported added in version 2.6.
+ required: False
username:
- description:
- - The username of the vSphere vCenter.
- required: True
- aliases: ['user', 'admin']
+ description:
+ - The username of the vSphere vCenter or ESXi server.
+ - If the value is not specified in the task, the value of environment variable C(VMWARE_USER) will be used instead.
+ - Environment variable supported added in version 2.6.
+ required: False
+ aliases: ['user', 'admin']
password:
- description:
- - The password of the vSphere vCenter.
- required: True
- aliases: ['pass', 'pwd']
+ description:
+ - The password of the vSphere vCenter or ESXi server.
+ - If the value is not specified in the task, the value of environment variable C(VMWARE_PASSWORD) will be used instead.
+ - Environment variable supported added in version 2.6.
+ required: False
+ aliases: ['pass', 'pwd']
validate_certs:
- description:
- - Allows connection when SSL certificates are not valid. Set to
- false when certificates are not trusted.
- default: 'True'
- type: bool
+ description:
+ - Allows connection when SSL certificates are not valid. Set to C(false) when certificates are not trusted.
+ - If the value is not specified in the task, the value of environment variable C(VMWARE_VALIDATE_CERTS) will be used instead.
+ - Environment variable supported added in version 2.6.
+ default: 'True'
+ type: bool
port:
- description:
- - The port number of the vSphere vCenter or ESXi server.
- required: False
- default: 443
- version_added: 2.5
+ description:
+ - The port number of the vSphere vCenter or ESXi server.
+ - If the value is not specified in the task, the value of environment variable C(VMWARE_PORT) will be used instead.
+ - Environment variable supported added in version 2.6.
+ required: False
+ default: 443
+ version_added: 2.5
'''
diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD
index f9031bd..ec7d927 100644
--- a/packaging/arch/PKGBUILD
+++ b/packaging/arch/PKGBUILD
@@ -47,7 +47,7 @@ package() {
install -Dm644 examples/ansible.cfg $pkgdir/etc/ansible/ansible.cfg
- install -Dm644 README.md $pkgdir/usr/share/doc/ansible/README.md
+ install -Dm644 README.rst $pkgdir/usr/share/doc/ansible/README.rst
install -Dm644 COPYING $pkgdir/usr/share/doc/ansible/COPYING
install -Dm644 CHANGELOG.md $pkgdir/usr/share/doc/ansible/CHANGELOG.md
diff --git a/packaging/debian/docs b/packaging/debian/docs
index b43bf86..a1320b1 100644
--- a/packaging/debian/docs
+++ b/packaging/debian/docs
@@ -1 +1 @@
-README.md
+README.rst
diff --git a/packaging/macports/sysutils/ansible/Portfile b/packaging/macports/sysutils/ansible/Portfile
index 638b261..9a386d7 100644
--- a/packaging/macports/sysutils/ansible/Portfile
+++ b/packaging/macports/sysutils/ansible/Portfile
@@ -45,7 +45,7 @@ patch {
post-destroot {
# documentation and examples
- xinstall -m 644 -W ${worksrcpath} README.md CHANGELOG.md CONTRIBUTING.md COPYING \
+ xinstall -m 644 -W ${worksrcpath} README.rst CHANGELOG.md CONTRIBUTING.md COPYING \
${destroot}${prefix}/share/doc/${name}
xinstall -m 755 -d ${destroot}${prefix}/share/doc/examples
diff --git a/packaging/release/vars/versions.yml b/packaging/release/vars/versions.yml
index ef500bd..152df88 100644
--- a/packaging/release/vars/versions.yml
+++ b/packaging/release/vars/versions.yml
@@ -3,6 +3,7 @@ versions:
code_name: "Kashmir"
releases:
- "0": "03-22-2018"
+ - "1": "04-18-2018"
- "2.4":
code_name: "Dancing Days"
releases:
diff --git a/packaging/rpm/ansible.spec b/packaging/rpm/ansible.spec
index cdff874..9f8069c 100644
--- a/packaging/rpm/ansible.spec
+++ b/packaging/rpm/ansible.spec
@@ -130,17 +130,23 @@ rm -rf %{buildroot}
%{_bindir}/ansible*
%dir %{_datadir}/ansible
%config(noreplace) %{_sysconfdir}/ansible
-%doc README.md PKG-INFO COPYING changelogs/CHANGELOG.rst
+%doc README.rst PKG-INFO COPYING changelogs/CHANGELOG.rst
%doc %{_mandir}/man1/ansible*
%changelog
+* Thu Apr 18 2018 Ansible, Inc. <info@ansible.com> - 2.5.1-1
+- Release 2.5.1-1
+
* Thu Mar 22 2018 Ansible, Inc. <info@ansible.com> - 2.5.0-1
- Release 2.5.0-1
* Wed Jan 31 2018 Ansible, Inc. <info@ansible.com> - 2.4.3.0-1
- Release 2.4.3.0-1
+* Wed Dec 20 2017 Ansible, Inc. <info@ansible.com> - 2.3.3.0-1
+- Release 2.3.3.0-1
+
* Wed Nov 29 2017 Ansible, Inc. <info@ansible.com> - 2.4.2.0-1
- Release 2.4.2.0-1
@@ -150,42 +156,39 @@ rm -rf %{buildroot}
* Mon Sep 18 2017 Ansible, Inc. <info@ansible.com> - 2.4.0.0-1
- Release 2.4.0.0-1
-* Wed Dec 20 2017 Ansible, Inc. <info@ansible.com> - 2.3.3.0-1
-- Release 2.3.3.0-1
-
* Fri Aug 04 2017 Ansible, Inc. <info@ansible.com> - 2.3.2.0-1
- Release 2.3.2.0-1
* Thu Jun 01 2017 Ansible, Inc. <info@ansible.com> - 2.3.1.0-1
- Release 2.3.1.0-1
-* Wed Apr 12 2017 Ansible, Inc. <info@ansible.com> - 2.3.0.0-1
-- Release 2.3.0.0-1
+* Thu Jun 01 2017 Ansible, Inc. <info@ansible.com> - 2.1.6.0-1
+- Release 2.1.6.0-1
* Tue May 09 2017 Ansible, Inc. <info@ansible.com> - 2.2.3.0-1
- Release 2.2.3.0-1
+* Wed Apr 12 2017 Ansible, Inc. <info@ansible.com> - 2.3.0.0-1
+- Release 2.3.0.0-1
+
* Mon Mar 27 2017 Ansible, Inc. <info@ansible.com> - 2.2.2.0-1
- Release 2.2.2.0-1
-* Mon Jan 16 2017 Ansible, Inc. <info@ansible.com> - 2.2.1.0-1
-- Release 2.2.1.0-1
-
-* Mon Oct 31 2016 Ansible, Inc. <info@ansible.com> - 2.2.0.0-1
-- Release 2.2.0.0-1
-
-* Thu Jun 01 2017 Ansible, Inc. <info@ansible.com> - 2.1.6.0-1
-- Release 2.1.6.0-1
-
* Mon Mar 27 2017 Ansible, Inc. <info@ansible.com> - 2.1.5.0-1
- Release 2.1.5.0-1
+* Mon Jan 16 2017 Ansible, Inc. <info@ansible.com> - 2.2.1.0-1
+- Release 2.2.1.0-1
+
* Mon Jan 16 2017 Ansible, Inc. <info@ansible.com> - 2.1.4.0-1
- Release 2.1.4.0-1
* Fri Nov 04 2016 Ansible, Inc. <info@ansible.com> - 2.1.3.0-1
- Release 2.1.3.0-1
+* Mon Oct 31 2016 Ansible, Inc. <info@ansible.com> - 2.2.0.0-1
+- Release 2.2.0.0-1
+
* Thu Sep 29 2016 Ansible, Inc. <info@ansible.com> - 2.1.2.0-1
- Release 2.1.2.0-1
diff --git a/setup.py b/setup.py
index ba874ba..29aed07 100644
--- a/setup.py
+++ b/setup.py
@@ -1,9 +1,13 @@
+from __future__ import print_function
+
import json
import os
import os.path
import re
import sys
+import warnings
+
from collections import defaultdict
from distutils.command.build_scripts import build_scripts as BuildScripts
from distutils.command.sdist import sdist as SDist
@@ -16,7 +20,7 @@ try:
except ImportError:
print("Ansible now needs setuptools in order to build. Install it using"
" your package manager (usually python-setuptools) or via pip (pip"
- " install setuptools).")
+ " install setuptools).", file=sys.stderr)
sys.exit(1)
sys.path.insert(0, os.path.abspath('lib'))
@@ -49,7 +53,7 @@ def _find_symlinks(topdir, extension=''):
def _cache_symlinks(symlink_data):
with open(SYMLINK_CACHE, 'w') as f:
- f.write(json.dumps(symlink_data))
+ json.dump(symlink_data, f)
def _maintain_symlinks(symlink_type, base_path):
@@ -58,7 +62,7 @@ def _maintain_symlinks(symlink_type, base_path):
# Try the cache first because going from git checkout to sdist is the
# only time we know that we're going to cache correctly
with open(SYMLINK_CACHE, 'r') as f:
- symlink_data = json.loads(f.read())
+ symlink_data = json.load(f)
except (IOError, OSError) as e:
# IOError on py2, OSError on py3. Both have errno
if e.errno == 2:
@@ -129,37 +133,87 @@ class SDistCommand(SDist):
SDist.run(self)
-with open('requirements.txt') as requirements_file:
- install_requirements = requirements_file.read().splitlines()
- if not install_requirements:
- print("Unable to read requirements from the requirements.txt file"
- "That indicates this copy of the source code is incomplete.")
- sys.exit(2)
-
-# pycrypto or cryptography. We choose a default but allow the user to
-# override it. This translates into pip install of the sdist deciding what
-# package to install and also the runtime dependencies that pkg_resources
-# knows about
-crypto_backend = os.environ.get('ANSIBLE_CRYPTO_BACKEND', None)
-if crypto_backend:
- if crypto_backend.strip() == 'pycrypto':
+def read_file(file_name):
+ """Read file and return its contents."""
+ with open(file_name, 'r') as f:
+ return f.read()
+
+
+def read_requirements(file_name):
+ """Read requirements file as a list."""
+ reqs = read_file(file_name).splitlines()
+ if not reqs:
+ raise RuntimeError(
+ "Unable to read requirements from the %s file"
+ "That indicates this copy of the source code is incomplete."
+ % file_name
+ )
+ return reqs
+
+
+PYCRYPTO_DIST = 'pycrypto'
+
+
+def get_crypto_req():
+ """Detect custom crypto from ANSIBLE_CRYPTO_BACKEND env var.
+
+ pycrypto or cryptography. We choose a default but allow the user to
+ override it. This translates into pip install of the sdist deciding what
+ package to install and also the runtime dependencies that pkg_resources
+ knows about.
+ """
+ crypto_backend = os.environ.get('ANSIBLE_CRYPTO_BACKEND', '').strip()
+
+ if crypto_backend == PYCRYPTO_DIST:
# Attempt to set version requirements
- crypto_backend = 'pycrypto >= 2.6'
+ return '%s >= 2.6' % PYCRYPTO_DIST
- install_requirements = [r for r in install_requirements if not (r.lower().startswith('pycrypto') or r.lower().startswith('cryptography'))]
- install_requirements.append(crypto_backend)
+ return crypto_backend or None
-# specify any extra requirements for installation
-extra_requirements = dict()
-extra_requirements_dir = 'packaging/requirements'
-for extra_requirements_filename in os.listdir(extra_requirements_dir):
- filename_match = re.search(r'^requirements-(\w*).txt$', extra_requirements_filename)
- if filename_match:
- with open(os.path.join(extra_requirements_dir, extra_requirements_filename)) as extra_requirements_file:
- extra_requirements[filename_match.group(1)] = extra_requirements_file.read().splitlines()
+def substitute_crypto_to_req(req):
+ """Replace crypto requirements if customized."""
+ crypto_backend = get_crypto_req()
-setup(
+ if crypto_backend is None:
+ return req
+
+ def is_not_crypto(r):
+ CRYPTO_LIBS = PYCRYPTO_DIST, 'cryptography'
+ return not any(r.lower().startswith(c) for c in CRYPTO_LIBS)
+
+ return [r for r in req if is_not_crypto(r)] + [crypto_backend]
+
+
+def read_extras():
+ """Specify any extra requirements for installation."""
+ extras = dict()
+ extra_requirements_dir = 'packaging/requirements'
+ for extra_requirements_filename in os.listdir(extra_requirements_dir):
+ filename_match = re.search(r'^requirements-(\w*).txt$', extra_requirements_filename)
+ if not filename_match:
+ continue
+ extra_req_file_path = os.path.join(extra_requirements_dir, extra_requirements_filename)
+ try:
+ extras[filename_match.group(1)] = read_file(extra_req_file_path).splitlines()
+ except RuntimeError:
+ pass
+ return extras
+
+
+def get_dynamic_setup_params():
+ """Add dynamically calculated setup params to static ones."""
+ return {
+ # Retrieve the long description from the README
+ 'long_description': read_file('README.rst'),
+ 'install_requires': substitute_crypto_to_req(
+ read_requirements('requirements.txt'),
+ ),
+ 'extras_require': read_extras(),
+ }
+
+
+static_setup_params = dict(
# Use the distutils SDist so that symlinks are not expanded
# Use a custom Build for the same reason
cmdclass={
@@ -175,10 +229,16 @@ setup(
author=__author__,
author_email='info@ansible.com',
url='https://ansible.com/',
+ project_urls={
+ 'Bug Tracker': 'https://github.com/ansible/ansible/issues',
+ 'CI: Shippable': 'https://app.shippable.com/github/ansible/ansible',
+ 'Documentation': 'https://docs.ansible.com/ansible/',
+ 'Source Code': 'https://github.com/ansible/ansible',
+ },
license='GPLv3+',
# Ansible will also make use of a system copy of python-six and
# python-selectors2 if installed but use a Bundled copy if it's not.
- install_requires=install_requirements,
+ python_requires='>=2.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*',
package_dir={'': 'lib'},
packages=find_packages('lib'),
package_data={
@@ -205,6 +265,9 @@ setup(
'Operating System :: POSIX',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
'Topic :: System :: Installation/Setup',
'Topic :: System :: Systems Administration',
'Topic :: Utilities',
@@ -222,7 +285,26 @@ setup(
'bin/ansible-inventory',
],
data_files=[],
- extras_require=extra_requirements,
# Installing as zip files would break due to references to __file__
zip_safe=False
)
+
+
+def main():
+ """Invoke installation process using setuptools."""
+ setup_params = dict(static_setup_params, **get_dynamic_setup_params())
+ ignore_warning_regex = (
+ r"Unknown distribution option: '(project_urls|python_requires)'"
+ )
+ warnings.filterwarnings(
+ 'ignore',
+ message=ignore_warning_regex,
+ category=UserWarning,
+ module='distutils.dist',
+ )
+ setup(**setup_params)
+ warnings.resetwarnings()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/integration/targets/azure_rm_networkinterface/tasks/main.yml b/test/integration/targets/azure_rm_networkinterface/tasks/main.yml
index 878ede7..ec1bd1f 100644
--- a/test/integration/targets/azure_rm_networkinterface/tasks/main.yml
+++ b/test/integration/targets/azure_rm_networkinterface/tasks/main.yml
@@ -134,6 +134,22 @@
- not output.state.ip_configuration
- output.state.ip_configurations | length == 2
+- name: IP configuration without public IP
+ azure_rm_networkinterface:
+ resource_group: "{{ resource_group }}"
+ name: testnic001noip
+ security_group: testnic001
+ virtual_network: "{{ vn.state.id }}"
+ subnet: testnic001
+ ip_configurations:
+ - name: ipconfig1
+ primary: True
+ register: output
+
+- assert:
+ that:
+ - output.state.ip_configurations[0].public_ip_address == None
+
- name: Delete the NIC (check mode)
azure_rm_networkinterface:
resource_group: "{{ resource_group }}"
diff --git a/test/integration/targets/docker_secret/tasks/test_secrets.yml b/test/integration/targets/docker_secret/tasks/test_secrets.yml
index 603eeae..c1b0a43 100644
--- a/test/integration/targets/docker_secret/tasks/test_secrets.yml
+++ b/test/integration/targets/docker_secret/tasks/test_secrets.yml
@@ -3,7 +3,7 @@
state: present
name: "{{ item }}"
with_items:
- - docker>=2.1.0
+ - docker==2.1.0
- name: Check if already in swarm
shell: docker node ls 2>&1 | grep 'docker swarm init'
diff --git a/test/integration/targets/ec2_asg/tasks/main.yml b/test/integration/targets/ec2_asg/tasks/main.yml
index 9b742a7..094458f 100644
--- a/test/integration/targets/ec2_asg/tasks/main.yml
+++ b/test/integration/targets/ec2_asg/tasks/main.yml
@@ -362,6 +362,122 @@
# ============================================================
+ # perform rolling replace with new launch configuration and lc_check:false
+
+ # Note - this is done async so we can query asg_facts during
+ # the execution. Issues #28087 and #35993 result in correct
+ # end result, but spin up extraneous instances during execution.
+ - name: "perform rolling update to new AMI with lc_check: false"
+ ec2_asg:
+ name: "{{ resource_prefix }}-asg"
+ launch_config_name: "{{ resource_prefix }}-lc-2"
+ health_check_type: EC2
+ desired_capacity: 3
+ min_size: 1
+ max_size: 5
+ health_check_period: 900
+ load_balancers: []
+ vpc_zone_identifier: "{{ testing_subnet.subnet.id }}"
+ wait_for_instances: yes
+ replace_all_instances: yes
+ replace_batch_size: 3
+ lc_check: false
+ wait_timeout: 1800
+ state: present
+ <<: *aws_connection_info
+ async: 1800
+ poll: 0
+ register: asg_job
+
+ - name: get ec2_asg facts for 3 minutes
+ ec2_asg_facts:
+ name: "{{ resource_prefix }}-asg"
+ <<: *aws_connection_info
+ register: output
+ loop_control:
+ pause: 15
+ with_sequence: count=12
+
+ - set_fact:
+ inst_id_json_query: 'results[*].results[*].instances[*].instance_id'
+
+ # Since we started with 3 servers and replace all of them.
+ # We should see 6 servers total.
+ - assert:
+ that:
+ - "lookup('flattened',output|json_query(inst_id_json_query)).split(',')|unique|length == 6"
+
+ - name: Ensure ec2_asg task completes
+ async_status: jid="{{ asg_job.ansible_job_id }}"
+ register: status
+ until: status.finished
+ retries: 200
+ delay: 15
+
+ # ============================================================
+
+ - name: kill asg
+ ec2_asg:
+ name: "{{ resource_prefix }}-asg"
+ state: absent
+ <<: *aws_connection_info
+ async: 300
+
+ # Create new asg with replace_all_instances and lc_check:false
+
+ # Note - this is done async so we can query asg_facts during
+ # the execution. Issues #28087 results in correct
+ # end result, but spin up extraneous instances during execution.
+ - name: "new asg with lc_check: false"
+ ec2_asg:
+ name: "{{ resource_prefix }}-asg"
+ launch_config_name: "{{ resource_prefix }}-lc"
+ health_check_type: EC2
+ desired_capacity: 3
+ min_size: 1
+ max_size: 5
+ health_check_period: 900
+ load_balancers: []
+ vpc_zone_identifier: "{{ testing_subnet.subnet.id }}"
+ wait_for_instances: yes
+ replace_all_instances: yes
+ replace_batch_size: 3
+ lc_check: false
+ wait_timeout: 1800
+ state: present
+ <<: *aws_connection_info
+ async: 1800
+ poll: 0
+ register: asg_job
+
+ # Collect ec2_asg_facts for 3 minutes
+ - name: get ec2_asg facts
+ ec2_asg_facts:
+ name: "{{ resource_prefix }}-asg"
+ <<: *aws_connection_info
+ register: output
+ loop_control:
+ pause: 15
+ with_sequence: count=12
+
+ - set_fact:
+ inst_id_json_query: 'results[*].results[*].instances[*].instance_id'
+
+ # Get all instance_ids we saw and assert we saw number expected
+ # Should only see 3 (don't replace instances we just created)
+ - assert:
+ that:
+ - "lookup('flattened',output|json_query(inst_id_json_query)).split(',')|unique|length == 3"
+
+ - name: Ensure ec2_asg task completes
+ async_status: jid="{{ asg_job.ansible_job_id }}"
+ register: status
+ until: status.finished
+ retries: 200
+ delay: 15
+
+# ============================================================
+
always:
- name: kill asg
diff --git a/test/integration/targets/ec2_group/tasks/main.yml b/test/integration/targets/ec2_group/tasks/main.yml
index ac1887d..b593b53 100644
--- a/test/integration/targets/ec2_group/tasks/main.yml
+++ b/test/integration/targets/ec2_group/tasks/main.yml
@@ -422,6 +422,8 @@
that:
- 'result.changed'
- 'result.group_id.startswith("sg-")'
+ - 'result.ip_permissions|length == 1'
+ - 'result.ip_permissions_egress|length == 1'
# ============================================================
- name: add same rule to the existing group (expected changed=false)
@@ -464,6 +466,7 @@
- result.ip_permissions|length == 2
- result.ip_permissions[0].user_id_group_pairs or
result.ip_permissions[1].user_id_group_pairs
+ - 'result.ip_permissions_egress[0].ip_protocol == "-1"'
# ============================================================
- name: test ip rules convert port numbers from string to int (expected changed=true)
@@ -489,6 +492,9 @@
that:
- 'result.changed'
- 'result.group_id.startswith("sg-")'
+ - 'result.ip_permissions|length == 1'
+ - 'result.ip_permissions_egress[0].ip_protocol == "tcp"'
+
# ============================================================
- name: test group rules convert port numbers from string to int (expected changed=true)
diff --git a/test/integration/targets/eos_config/templates/basic/cmds.j2 b/test/integration/targets/eos_config/templates/basic/cmds.j2
new file mode 100644
index 0000000..c68cce8
--- /dev/null
+++ b/test/integration/targets/eos_config/templates/basic/cmds.j2
@@ -0,0 +1,4 @@
+ip access-list test
+ 10 permit ip host 192.168.0.2 host 192.168.0.1
+ 20 permit ip host 192.168.0.1 host 192.168.0.2
+!
diff --git a/test/integration/targets/eos_config/tests/cli/check_mode.yaml b/test/integration/targets/eos_config/tests/cli/check_mode.yaml
new file mode 100644
index 0000000..42f5d19
--- /dev/null
+++ b/test/integration/targets/eos_config/tests/cli/check_mode.yaml
@@ -0,0 +1,69 @@
+---
+- debug: msg="START cli/check_mode.yaml on connection={{ ansible_connection }}"
+
+- name: invalid configuration in check mode
+ eos_config:
+ lines:
+ - ip address 119.31.1.1 255.255.255.256
+ parents: interface Loopback911
+ check_mode: 1
+ environment:
+ ANSIBLE_EOS_USE_SESSIONS: 1
+ register: result
+ ignore_errors: yes
+
+- assert:
+ that:
+ - "result.msg is defined"
+ - "result.failed == true"
+ - "'Error on executing commands' in result.msg"
+
+- name: valid configuration in check mode
+ eos_config:
+ before:
+ - "no ip access-list test"
+ src: basic/cmds.j2
+ check_mode: yes
+ register: config
+
+- name: check if session is removed
+ eos_command:
+ commands:
+ - show configuration sessions | json
+ provider: "{{ cli }}"
+ register: result
+
+- assert:
+ that:
+ - "config.session not in result.stdout[0].sessions"
+
+- name: invalid configuration in check mode + no config session
+ eos_config:
+ lines:
+ - ip address 119.31.1.1 255.255.255.256
+ parents: interface Loopback911
+ check_mode: 1
+ environment:
+ ANSIBLE_EOS_USE_SESSIONS: 0
+ register: result
+ ignore_errors: yes
+
+- assert:
+ that:
+ - "result.changed == true"
+
+- name: valid configuration in check mode + no config session
+ eos_config:
+ lines:
+ - ip address 119.31.1.1 255.255.255.255
+ parents: interface Loopback911
+ check_mode: yes
+ register: result
+ environment:
+ ANSIBLE_EOS_USE_SESSIONS: 0
+
+- assert:
+ that:
+ - "result.changed == true"
+
+- debug: msg="END cli/check_mode.yaml on connection={{ ansible_connection }}"
diff --git a/test/integration/targets/eos_facts/tests/eapi/default_facts.yaml b/test/integration/targets/eos_facts/tests/eapi/default_facts.yaml
index fbf987a..2892746 100644
--- a/test/integration/targets/eos_facts/tests/eapi/default_facts.yaml
+++ b/test/integration/targets/eos_facts/tests/eapi/default_facts.yaml
@@ -1,6 +1,11 @@
---
- debug: msg="START eapi/default_facts.yaml"
+- name: Make sure LLDP is running (setup)
+ eos_config:
+ lines: lldp run
+ authorize: yes
+ provider: "{{ eapi }}"
- name: test getting default facts
eos_facts:
@@ -28,4 +33,10 @@
# ... and not present
- "result.ansible_facts.ansible_net_config is not defined" # config
+- name: Make sure LLDP is running (setup)
+ eos_config:
+ lines: lldp run
+ authorize: yes
+ provider: "{{ eapi }}"
+
- debug: msg="END eapi/default.yaml"
diff --git a/test/integration/targets/eos_facts/tests/eapi/not_hardware.yaml b/test/integration/targets/eos_facts/tests/eapi/not_hardware.yaml
index 9267f63..46b54a6 100644
--- a/test/integration/targets/eos_facts/tests/eapi/not_hardware.yaml
+++ b/test/integration/targets/eos_facts/tests/eapi/not_hardware.yaml
@@ -1,6 +1,11 @@
---
- debug: msg="START eapi/not_hardware_facts.yaml"
+- name: Make sure LLDP is running (setup)
+ eos_config:
+ lines: lldp run
+ authorize: yes
+ provider: "{{ eapi }}"
- name: test not hardware
eos_facts:
@@ -27,4 +32,10 @@
# ... and not present
- "result.ansible_facts.ansible_net_filesystems is not defined"
+- name: Make sure LLDP is running (teardown)
+ eos_config:
+ lines: no lldp run
+ authorize: yes
+ provider: "{{ eapi }}"
+
- debug: msg="END eapi/not_hardware_facts.yaml"
diff --git a/test/integration/targets/eos_smoke/defaults/main.yaml b/test/integration/targets/eos_smoke/defaults/main.yaml
new file mode 100644
index 0000000..5f709c5
--- /dev/null
+++ b/test/integration/targets/eos_smoke/defaults/main.yaml
@@ -0,0 +1,2 @@
+---
+testcase: "*"
diff --git a/test/integration/targets/eos_smoke/meta/main.yml b/test/integration/targets/eos_smoke/meta/main.yml
new file mode 100644
index 0000000..e5c8cd0
--- /dev/null
+++ b/test/integration/targets/eos_smoke/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+ - prepare_eos_tests
diff --git a/test/integration/targets/eos_smoke/tasks/cli.yaml b/test/integration/targets/eos_smoke/tasks/cli.yaml
new file mode 100644
index 0000000..a6f7ae0
--- /dev/null
+++ b/test/integration/targets/eos_smoke/tasks/cli.yaml
@@ -0,0 +1,22 @@
+---
+- name: collect all cli test cases
+ find:
+ paths: "{{ role_path }}/tests/cli"
+ patterns: "{{ testcase }}.yaml"
+ register: test_cases
+ delegate_to: localhost
+
+- name: set test_items
+ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test cases (connection=network_cli)
+ include: "{{ test_case_to_run }} ansible_connection=network_cli"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
+
+- name: run test case (connection=local)
+ include: "{{ test_case_to_run }} ansible_connection=local ansible_become=no"
+ with_first_found: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/test/integration/targets/eos_smoke/tasks/eapi.yaml b/test/integration/targets/eos_smoke/tasks/eapi.yaml
new file mode 100644
index 0000000..bda1df6
--- /dev/null
+++ b/test/integration/targets/eos_smoke/tasks/eapi.yaml
@@ -0,0 +1,16 @@
+---
+- name: collect all eapi test cases
+ find:
+ paths: "{{ role_path }}/tests/eapi"
+ patterns: "{{ testcase }}.yaml"
+ delegate_to: localhost
+ register: test_cases
+
+- name: set test_items
+ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test case (connection=local)
+ include: "{{ test_case_to_run }} ansible_connection=local"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/test/integration/targets/eos_smoke/tasks/main.yaml b/test/integration/targets/eos_smoke/tasks/main.yaml
new file mode 100644
index 0000000..970e741
--- /dev/null
+++ b/test/integration/targets/eos_smoke/tasks/main.yaml
@@ -0,0 +1,3 @@
+---
+- { include: cli.yaml, tags: ['cli'] }
+- { include: eapi.yaml, tags: ['eapi'] }
diff --git a/test/integration/targets/eos_smoke/tests/cli/common_config.yaml b/test/integration/targets/eos_smoke/tests/cli/common_config.yaml
new file mode 100644
index 0000000..ca631e2
--- /dev/null
+++ b/test/integration/targets/eos_smoke/tests/cli/common_config.yaml
@@ -0,0 +1,108 @@
+---
+# eos_config -> NetworkConfig, dumps
+
+- debug: msg="START cli/common_config.yaml on connection={{ ansible_connection }}"
+
+- name: setup
+ eos_config:
+ lines: hostname {{ inventory_hostname_short }}
+ match: none
+ provider: "{{ cli }}"
+ become: yes
+
+- name: get current running-config
+ eos_command:
+ commands: show running-config
+ provider: "{{ cli }}"
+ become: yes
+ register: config
+
+- name: configure hostname
+ eos_config:
+ lines: hostname foo
+ config: "{{ config.stdout[0] }}"
+ provider: "{{ cli }}"
+ become: yes
+ register: result
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'hostname foo' in result.updates"
+
+- name: get current running-config
+ eos_command:
+ commands: show running-config
+ provider: "{{ cli }}"
+ become: yes
+ register: config
+
+- name: teardown
+ eos_config:
+ lines: hostname {{ inventory_hostname_short }}
+ match: none
+ provider: "{{ cli }}"
+ become: yes
+
+# hit block and diffs
+
+- name: setup
+ eos_config:
+ lines:
+ - 10 permit ip host 1.1.1.1 any log
+ - 20 permit ip host 2.2.2.2 any log
+ - 30 permit ip host 3.3.3.3 any log
+ parents: ip access-list test
+ before: no ip access-list test
+ after: exit
+ match: strict
+ provider: "{{ cli }}"
+ become: yes
+
+- name: configure sub level command using block resplace
+ eos_config:
+ lines:
+ - 10 permit ip host 1.1.1.1 any log
+ - 20 permit ip host 2.2.2.2 any log
+ - 30 permit ip host 3.3.3.3 any log
+ - 40 permit ip host 4.4.4.4 any log
+ parents: ip access-list test
+ replace: block
+ after: exit
+ provider: "{{ cli }}"
+ match: line
+ become: yes
+ register: result
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'ip access-list test' in result.updates"
+ - "'10 permit ip host 1.1.1.1 any log' in result.updates"
+ - "'20 permit ip host 2.2.2.2 any log' in result.updates"
+ - "'30 permit ip host 3.3.3.3 any log' in result.updates"
+ - "'40 permit ip host 4.4.4.4 any log' in result.updates"
+
+- name: check sub level command using block replace
+ eos_config:
+ lines:
+ - 10 permit ip host 1.1.1.1 any log
+ - 20 permit ip host 2.2.2.2 any log
+ - 30 permit ip host 3.3.3.3 any log
+ - 40 permit ip host 4.4.4.4 any log
+ parents: ip access-list test
+ replace: block
+ after: exit
+ provider: "{{ cli }}"
+ match: exact
+ become: yes
+ register: result
+
+- name: teardown
+ eos_config:
+ lines: no ip access-list test
+ match: none
+ provider: "{{ cli }}"
+ become: yes
+
+- debug: msg="END cli/common_config.yaml on connection={{ ansible_connection }}"
diff --git a/test/integration/targets/eos_smoke/tests/cli/common_utils.yaml b/test/integration/targets/eos_smoke/tests/cli/common_utils.yaml
new file mode 100644
index 0000000..4f0242b
--- /dev/null
+++ b/test/integration/targets/eos_smoke/tests/cli/common_utils.yaml
@@ -0,0 +1,72 @@
+---
+# eos_static_route -> remove_default_spec, validate_ip_address, validate_prefix
+# eos_interface -> conditional
+# eos_command -> ComplexList
+
+- debug: msg="START cli/common_utils.yaml on connection={{ ansible_connection }}"
+
+# hit remove_default_spec() validate_ip_address() validate_prefix() ComplexList
+- name: setup - remove config used in test
+ eos_config:
+ lines:
+ - no ip route 192.168.3.0/24 192.168.0.1
+ authorize: yes
+ provider: "{{ cli }}"
+ become: yes
+
+- name: configure static route
+ eos_static_route:
+ address: 192.168.3.0/24
+ next_hop: 192.168.0.1
+ admin_distance: 2
+ authorize: yes
+ provider: "{{ cli }}"
+ become: yes
+ register: result
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'ip route 192.168.3.0/24 192.168.0.1 2' in result.commands"
+
+- name: configure static route
+ eos_static_route:
+ address: 192.168.3.0/250
+ next_hop: 192.168.0.1
+ admin_distance: 2
+ authorize: yes
+ provider: "{{ cli }}"
+ become: yes
+ register: result
+ ignore_errors: yes
+
+- assert:
+ that:
+ - "result.failed == true"
+
+- name: teardown
+ eos_config:
+ lines:
+ - no ip route 192.168.3.0/24 192.168.0.1
+ authorize: yes
+ provider: "{{ cli }}"
+ become: yes
+
+- debug: msg="END cli/common_utils.yaml on connection={{ ansible_connection }}"
+
+
+# hit conditional()
+- name: Set test interface
+ set_fact:
+ test_interface_1: ethernet1
+
+- name: Check intent arguments
+ eos_interface:
+ name: "{{ test_interface_1 }}"
+ state: up
+ tx_rate: ge(0)
+ rx_rate: ge(0)
+ authorize: yes
+ provider: "{{ cli }}"
+ become: yes
+ register: result
diff --git a/test/integration/targets/eos_smoke/tests/cli/misc_tests.yaml b/test/integration/targets/eos_smoke/tests/cli/misc_tests.yaml
new file mode 100644
index 0000000..3d8297e
--- /dev/null
+++ b/test/integration/targets/eos_smoke/tests/cli/misc_tests.yaml
@@ -0,0 +1,26 @@
+---
+- debug: msg="START cli/misc_tests.yaml on connection={{ ansible_connection }}"
+
+
+# test become and unbecome
+- block:
+ - name: command that does require become (should fail)
+ eos_command:
+ commands: show running-config
+ provider: "{{ cli }}"
+ become: no
+ ignore_errors: yes
+ register: result
+
+ - assert:
+ that:
+ - 'result.failed == true'
+ - '"privileged mode required" in result.module_stderr'
+
+ - name: command that doesn't require become
+ eos_command:
+ commands: show uptime
+ provider: "{{ cli }}"
+ become: no
+
+ when: "ansible_connection != 'local'"
diff --git a/test/integration/targets/eos_smoke/tests/eapi/common_config.yaml b/test/integration/targets/eos_smoke/tests/eapi/common_config.yaml
new file mode 100644
index 0000000..da80c00
--- /dev/null
+++ b/test/integration/targets/eos_smoke/tests/eapi/common_config.yaml
@@ -0,0 +1,99 @@
+---
+# eos_config -> NetworkConfig, dumps
+
+- debug: msg="START cli/common_config.yaml on connection={{ ansible_connection }}"
+
+- name: setup
+ eos_config:
+ lines: hostname {{ inventory_hostname_short }}
+ match: none
+ provider: "{{ eapi }}"
+
+- name: get current running-config
+ eos_command:
+ commands: show running-config
+ provider: "{{ eapi }}"
+ register: config
+
+- name: configure hostname
+ eos_config:
+ lines: hostname foo
+ config: "{{ config.stdout[0] }}"
+ provider: "{{ eapi }}"
+ register: result
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'hostname foo' in result.updates"
+
+- name: get current running-config
+ eos_command:
+ commands: show running-config
+ provider: "{{ eapi }}"
+ register: config
+
+- name: teardown
+ eos_config:
+ lines: hostname {{ inventory_hostname_short }}
+ match: none
+ provider: "{{ eapi }}"
+
+# hit block and diffs
+
+- name: setup
+ eos_config:
+ lines:
+ - 10 permit ip host 1.1.1.1 any log
+ - 20 permit ip host 2.2.2.2 any log
+ - 30 permit ip host 3.3.3.3 any log
+ parents: ip access-list test
+ before: no ip access-list test
+ after: exit
+ match: strict
+ provider: "{{ eapi }}"
+
+- name: configure sub level command using block resplace
+ eos_config:
+ lines:
+ - 10 permit ip host 1.1.1.1 any log
+ - 20 permit ip host 2.2.2.2 any log
+ - 30 permit ip host 3.3.3.3 any log
+ - 40 permit ip host 4.4.4.4 any log
+ parents: ip access-list test
+ replace: block
+ after: exit
+ provider: "{{ eapi }}"
+ match: line
+ register: result
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'ip access-list test' in result.updates"
+ - "'10 permit ip host 1.1.1.1 any log' in result.updates"
+ - "'20 permit ip host 2.2.2.2 any log' in result.updates"
+ - "'30 permit ip host 3.3.3.3 any log' in result.updates"
+ - "'40 permit ip host 4.4.4.4 any log' in result.updates"
+
+- name: check sub level command using block replace
+ eos_config:
+ lines:
+ - 10 permit ip host 1.1.1.1 any log
+ - 20 permit ip host 2.2.2.2 any log
+ - 30 permit ip host 3.3.3.3 any log
+ - 40 permit ip host 4.4.4.4 any log
+ parents: ip access-list test
+ replace: block
+ after: exit
+ provider: "{{ eapi }}"
+ match: exact
+ register: result
+
+- name: teardown
+ eos_config:
+ lines: no ip access-list test
+ match: none
+ provider: "{{ eapi }}"
+
+- debug: msg="END cli/common_config.yaml on connection={{ ansible_connection }}"
diff --git a/test/integration/targets/eos_smoke/tests/eapi/common_utils.yaml b/test/integration/targets/eos_smoke/tests/eapi/common_utils.yaml
new file mode 100644
index 0000000..7d916ad
--- /dev/null
+++ b/test/integration/targets/eos_smoke/tests/eapi/common_utils.yaml
@@ -0,0 +1,66 @@
+---
+# eos_static_route -> remove_default_spec, validate_ip_address, validate_prefix
+# eos_interface -> conditional
+# eos_command -> ComplexList
+
+- debug: msg="START cli/common_utils.yaml on connection={{ ansible_connection }}"
+
+# hit remove_default_spec() validate_ip_address() validate_prefix() ComplexList
+- name: setup - remove config used in test
+ eos_config:
+ lines:
+ - no ip route 192.168.3.0/24 192.168.0.1
+ authorize: yes
+ provider: "{{ eapi }}"
+
+- name: configure static route
+ eos_static_route:
+ address: 192.168.3.0/24
+ next_hop: 192.168.0.1
+ admin_distance: 2
+ authorize: yes
+ provider: "{{ eapi }}"
+ register: result
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'ip route 192.168.3.0/24 192.168.0.1 2' in result.commands"
+
+- name: configure static route
+ eos_static_route:
+ address: 192.168.3.0/250
+ next_hop: 192.168.0.1
+ admin_distance: 2
+ authorize: yes
+ provider: "{{ eapi }}"
+ register: result
+ ignore_errors: yes
+
+- assert:
+ that:
+ - "result.failed == true"
+
+- name: teardown
+ eos_config:
+ lines:
+ - no ip route 192.168.3.0/24 192.168.0.1
+ authorize: yes
+ provider: "{{ eapi }}"
+
+- debug: msg="END cli/common_utils.yaml on connection={{ ansible_connection }}"
+
+# hit conditional()
+- name: Set test interface
+ set_fact:
+ test_interface_1: ethernet1
+
+- name: Check intent arguments
+ eos_interface:
+ name: "{{ test_interface_1 }}"
+ state: up
+ tx_rate: ge(0)
+ rx_rate: ge(0)
+ authorize: yes
+ provider: "{{ eapi }}"
+ register: result
diff --git a/test/integration/targets/eos_smoke/tests/eapi/misc_tests.yaml b/test/integration/targets/eos_smoke/tests/eapi/misc_tests.yaml
new file mode 100644
index 0000000..9d089f7
--- /dev/null
+++ b/test/integration/targets/eos_smoke/tests/eapi/misc_tests.yaml
@@ -0,0 +1,26 @@
+---
+- debug: msg="START cli/misc_tests.yaml on connection={{ ansible_connection }}"
+
+
+# test become and unbecome
+- block:
+ - name: command that does require become (should fail)
+ eos_command:
+ commands: show running-config
+ provider: "{{ eapi }}"
+ become: no
+ ignore_errors: yes
+ register: result
+
+ - assert:
+ that:
+ - 'result.failed == true'
+ - '"privileged mode required" in result.module_stderr'
+
+ - name: command that doesn't require become
+ eos_command:
+ commands: show uptime
+ provider: "{{ eapi }}"
+ become: no
+
+ when: "ansible_connection != 'local'"
diff --git a/test/integration/targets/fortios_address/aliases b/test/integration/targets/fortios_address/aliases
index 7978f4a..cf28a97 100644
--- a/test/integration/targets/fortios_address/aliases
+++ b/test/integration/targets/fortios_address/aliases
@@ -1,2 +1 @@
-posix/ci/group1
destructive
diff --git a/test/integration/targets/fortios_ipv4_policy/aliases b/test/integration/targets/fortios_ipv4_policy/aliases
index 7978f4a..cf28a97 100644
--- a/test/integration/targets/fortios_ipv4_policy/aliases
+++ b/test/integration/targets/fortios_ipv4_policy/aliases
@@ -1,2 +1 @@
-posix/ci/group1
destructive
diff --git a/test/integration/targets/include_import/role/test_include_role.yml b/test/integration/targets/include_import/role/test_include_role.yml
index 79f4a78..2e20a14 100644
--- a/test/integration/targets/include_import/role/test_include_role.yml
+++ b/test/integration/targets/include_import/role/test_include_role.yml
@@ -47,12 +47,18 @@
- name: Test role include with a loop
include_role:
name: "{{ item }}"
- register: loop_test
with_items:
- role1
- role3
- role2
+ - name: Assert that roles run with_items
+ assert:
+ that:
+ - _role1_result.msg == 'In role1'
+ - _role2_result.msg == 'In role2'
+ - _role3_result.msg == 'In role3'
+
- name: Test including a task file from a role
include_role:
name: role1
diff --git a/test/integration/targets/include_import/roles/role2/tasks/main.yml b/test/integration/targets/include_import/roles/role2/tasks/main.yml
index 80d6a81..82934f6 100644
--- a/test/integration/targets/include_import/roles/role2/tasks/main.yml
+++ b/test/integration/targets/include_import/roles/role2/tasks/main.yml
@@ -1,2 +1,3 @@
- debug:
msg: In role2
+ register: _role2_result
diff --git a/test/integration/targets/include_import/roles/role3/tasks/main.yml b/test/integration/targets/include_import/roles/role3/tasks/main.yml
index 76608a9..bb70dad 100644
--- a/test/integration/targets/include_import/roles/role3/tasks/main.yml
+++ b/test/integration/targets/include_import/roles/role3/tasks/main.yml
@@ -1,2 +1,3 @@
- debug:
msg: In role3
+ register: _role3_result
diff --git a/test/integration/targets/ios_l2_interface/tests/cli/sanity.yaml b/test/integration/targets/ios_l2_interface/tests/cli/sanity.yaml
index b0596d4..44466f9 100644
--- a/test/integration/targets/ios_l2_interface/tests/cli/sanity.yaml
+++ b/test/integration/targets/ios_l2_interface/tests/cli/sanity.yaml
@@ -88,23 +88,52 @@
- assert: *false
- - name: Ensure these VLANs are not being tagged on the trunk
+ - name: Remove full trunk vlan range 2-50
ios_l2_interface: &no_tag
name: "{{ test_interface }}"
mode: trunk
- trunk_vlans: 30-4094
+ trunk_vlans: 2-50
state: absent
provider: "{{ cli }}"
register: result
- assert: *true
- - name: "no tag vlan Idempotence"
+ - name: Check Idempotence Remove full trunk vlan range 2-50
ios_l2_interface: *no_tag
register: result
- assert: *false
+ - name: Reconfigure interface trunk port and ensure 2-50 are being tagged
+ ios_l2_interface: *tag
+ register: result
+
+ - assert: *true
+
+ - name: Check Idempotence Reconfigure interface trunk port and ensure 2-50 are being tagged
+ ios_l2_interface: *tag
+ register: result
+
+ - assert: *false
+
+ - name: Remove partial trunk vlan range 30-4094 are removed
+ ios_l2_interface: &partial
+ name: "{{ test_interface }}"
+ mode: trunk
+ trunk_vlans: 30-4094
+ state: absent
+ provider: "{{ cli }}"
+ register: result
+
+ - assert: *true
+
+ - name: Check Idempotence Remove partial trunk vlan range 30-4094 are removed
+ ios_l2_interface: *partial
+ register: result
+
+ - assert: *false
+
- name: put interface default state
ios_l2_interface: *def_swi
register: result
diff --git a/test/integration/targets/iosxr_smoke/defaults/main.yaml b/test/integration/targets/iosxr_smoke/defaults/main.yaml
new file mode 100644
index 0000000..9ef5ba5
--- /dev/null
+++ b/test/integration/targets/iosxr_smoke/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "*"
+test_items: []
diff --git a/test/integration/targets/iosxr_smoke/meta/main.yaml b/test/integration/targets/iosxr_smoke/meta/main.yaml
new file mode 100644
index 0000000..d4da833
--- /dev/null
+++ b/test/integration/targets/iosxr_smoke/meta/main.yaml
@@ -0,0 +1,2 @@
+dependencies:
+ - prepare_iosxr_tests
diff --git a/test/integration/targets/iosxr_smoke/tasks/cli.yaml b/test/integration/targets/iosxr_smoke/tasks/cli.yaml
new file mode 100644
index 0000000..97dfa2c
--- /dev/null
+++ b/test/integration/targets/iosxr_smoke/tasks/cli.yaml
@@ -0,0 +1,24 @@
+---
+- name: collect all cli test cases
+ find:
+ paths: "{{ role_path }}/tests/cli"
+ patterns: "{{ testcase }}.yaml"
+ register: test_cases
+ delegate_to: localhost
+
+- name: set test_items
+ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test case (connection=network_cli)
+ include: "{{ test_case_to_run }} ansible_connection=network_cli"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
+
+# Only one of the Testcase would be run to check if `connection: local`
+# is established. Full suite is run with `connection:network_cli` or `connection:netconf`
+- name: run test case (connection=local)
+ include: "{{ test_case_to_run }} ansible_connection=local"
+ with_first_found: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/test/integration/targets/iosxr_smoke/tasks/main.yaml b/test/integration/targets/iosxr_smoke/tasks/main.yaml
new file mode 100644
index 0000000..af08869
--- /dev/null
+++ b/test/integration/targets/iosxr_smoke/tasks/main.yaml
@@ -0,0 +1,3 @@
+---
+- { include: cli.yaml, tags: ['cli'] }
+- { include: netconf.yaml, tags: ['netconf'] }
diff --git a/test/integration/targets/iosxr_smoke/tasks/netconf.yaml b/test/integration/targets/iosxr_smoke/tasks/netconf.yaml
new file mode 100644
index 0000000..904db03
--- /dev/null
+++ b/test/integration/targets/iosxr_smoke/tasks/netconf.yaml
@@ -0,0 +1,24 @@
+---
+- name: collect all netconf test cases
+ find:
+ paths: "{{ role_path }}/tests/netconf"
+ patterns: "{{ testcase }}.yaml"
+ register: test_cases
+ delegate_to: localhost
+
+- name: set test_items
+ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test cases (connection=netconf)
+ include: "{{ test_case_to_run }} ansible_connection=netconf"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
+
+# Only one of the Testcase would be run to check if `connection: local`
+# is established. Full suite is run with `connection:network_cli` or `connection:netconf`
+- name: run test case (connection=local)
+ include: "{{ test_case_to_run }} ansible_connection=local"
+ with_first_found: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/test/integration/targets/iosxr_smoke/tests/cli/common_config.yaml b/test/integration/targets/iosxr_smoke/tests/cli/common_config.yaml
new file mode 100644
index 0000000..d77a424
--- /dev/null
+++ b/test/integration/targets/iosxr_smoke/tests/cli/common_config.yaml
@@ -0,0 +1,100 @@
+---
+- debug: msg="START cli/common_config.yaml on connection={{ ansible_connection }}"
+
+# Sublevel / Block
+- name: setup
+ iosxr_config:
+ commands:
+ - 10 permit ipv4 host 1.1.1.1 any log
+ - 20 permit ipv4 host 2.2.2.2 any log
+ - 30 permit ipv4 host 3.3.3.3 any log
+ parents: ['ipv4 access-list test']
+ before: ['no ipv4 access-list test']
+ match: none
+
+- name: configure sub level command using block resplace
+ iosxr_config:
+ commands:
+ - 10 permit ipv4 host 1.1.1.1 any log
+ - 20 permit ipv4 host 2.2.2.2 any log
+ - 30 permit ipv4 host 3.3.3.3 any log
+ - 40 permit ipv4 host 4.4.4.4 any log
+ parents: ['ipv4 access-list test']
+ replace: block
+ register: result
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'ipv4 access-list test' in result.commands"
+ - "'10 permit ipv4 host 1.1.1.1 any log' in result.commands"
+ - "'20 permit ipv4 host 2.2.2.2 any log' in result.commands"
+ - "'30 permit ipv4 host 3.3.3.3 any log' in result.commands"
+ - "'40 permit ipv4 host 4.4.4.4 any log' in result.commands"
+
+- name: check sub level command using block replace
+ iosxr_config:
+ commands:
+ - 10 permit ipv4 host 1.1.1.1 any log
+ - 20 permit ipv4 host 2.2.2.2 any log
+ - 30 permit ipv4 host 3.3.3.3 any log
+ - 40 permit ipv4 host 4.4.4.4 any log
+ parents: ['ipv4 access-list test']
+ replace: block
+ register: result
+
+- assert:
+ that:
+ - "result.changed == false"
+
+- name: teardown
+ iosxr_config:
+ commands: ['no ipv4 access-list test']
+ match: none
+
+# diff exact, strict, line
+- name: setup
+ iosxr_config:
+ commands:
+ - 'hostname {{ inventory_hostname_short }}'
+ register: result
+
+- name: set hostname
+ iosxr_config:
+ commands:
+ - hostname testhost
+ match: strict
+ register: result
+
+- iosxr_command:
+ commands:
+ - show configuration running-config hostname
+ register: configured_hostname
+
+- assert:
+ that:
+ - "'testhost' in configured_hostname.stdout[0]"
+
+- name: set hostname
+ iosxr_config:
+ commands:
+ - hostname testhost2
+ match: exact
+ register: result
+
+- iosxr_command:
+ commands:
+ - show configuration running-config hostname
+ register: configured_hostname
+
+- assert:
+ that:
+ - "'testhost2' in configured_hostname.stdout[0]"
+
+- name: teardown
+ iosxr_config:
+ commands:
+ - 'hostname {{ inventory_hostname_short }}'
+ register: result
+
+- debug: msg="END cli/common_config.yaml on connection={{ ansible_connection }}"
diff --git a/test/integration/targets/iosxr_smoke/tests/cli/common_utils.yaml b/test/integration/targets/iosxr_smoke/tests/cli/common_utils.yaml
new file mode 100644
index 0000000..f1dfeff
--- /dev/null
+++ b/test/integration/targets/iosxr_smoke/tests/cli/common_utils.yaml
@@ -0,0 +1,37 @@
+---
+- debug: msg="START iosxr cli/common_utils.yaml on connection={{ ansible_connection }}"
+
+# Functions used by iosxr: conditional, remove_default_spec
+
+# hit conditional() and remove_default_spec()
+- name: Check intent arguments
+ iosxr_interface:
+ name: GigabitEthernet0/0/0/1
+ state: up
+ tx_rate: ge(0)
+ rx_rate: ge(0)
+ provider: "{{ cli }}"
+ register: result
+
+- assert:
+ that:
+ - "result.failed == false"
+
+- name: Check intent arguments (failed condition)
+ iosxr_interface:
+ name: GigabitEthernet0/0/0/1
+ state: down
+ tx_rate: gt(0)
+ rx_rate: lt(0)
+ provider: "{{ cli }}"
+ ignore_errors: yes
+ register: result
+
+- assert:
+ that:
+ - "result.failed == true"
+ - "'state eq(down)' in result.failed_conditions"
+ - "'tx_rate gt(0)' in result.failed_conditions"
+ - "'rx_rate lt(0)' in result.failed_conditions"
+
+- debug: msg="END iosxr cli/common_utils.yaml on connection={{ ansible_connection }}"
diff --git a/test/integration/targets/iosxr_smoke/tests/netconf/common_netconf.yaml b/test/integration/targets/iosxr_smoke/tests/netconf/common_netconf.yaml
new file mode 100644
index 0000000..3463cc2
--- /dev/null
+++ b/test/integration/targets/iosxr_smoke/tests/netconf/common_netconf.yaml
@@ -0,0 +1,53 @@
+---
+- debug: msg="START iosxr netconf/common_netconf.yaml on connection={{ ansible_connection }}"
+
+# hit general code
+- name: setup - remove login
+ iosxr_banner:
+ banner: login
+ provider: "{{ netconf }}"
+ state: absent
+
+- name: Set login
+ iosxr_banner:
+ banner: login
+ text: |
+ this is my login banner
+ that has a multiline
+ string
+ provider: "{{ netconf }}"
+ state: present
+ register: result
+
+- debug:
+ msg: "{{ result }}"
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'this is my login banner' in result.xml"
+ - "'that has a multiline' in result.xml"
+
+# hit etree_findall()
+- name: remove host logging
+ iosxr_logging:
+ dest: host
+ name: 172.16.0.1
+ state: absent
+ provider: "{{ netconf }}"
+
+- name: set up syslog host logging
+ iosxr_logging: &addhostlog
+ dest: host
+ name: 172.16.0.1
+ level: errors
+ state: present
+ provider: "{{ netconf }}"
+ register: result
+
+- assert:
+ that:
+ - 'result.changed == true'
+ - '"172.16.0.1" in result.xml[0]'
+
+- debug: msg="END iosxr netconf/common_netconf.yaml on connection={{ ansible_connection }}"
diff --git a/test/integration/targets/iosxr_smoke/tests/netconf/misc_tests.yaml b/test/integration/targets/iosxr_smoke/tests/netconf/misc_tests.yaml
new file mode 100644
index 0000000..fc5df1f
--- /dev/null
+++ b/test/integration/targets/iosxr_smoke/tests/netconf/misc_tests.yaml
@@ -0,0 +1,39 @@
+- debug: msg="START iosxr netconf/misc_tests.yaml on connection={{ ansible_connection }}"
+
+
+# hit module_utils.network.iosxr -> get_oper()
+- name: Setup (interface is up)
+ iosxr_interface:
+ name: GigabitEthernet0/0/0/1
+ description: test_interface_1
+ enabled: True
+ state: present
+ provider: "{{ netconf }}"
+ register: result
+
+- name: Check intent arguments
+ iosxr_interface:
+ name: GigabitEthernet0/0/0/1
+ state: up
+ delay: 10
+ provider: "{{ netconf }}"
+ register: result
+
+- assert:
+ that:
+ - "result.failed == false"
+
+- name: Check intent arguments (failed condition)
+ iosxr_interface:
+ name: GigabitEthernet0/0/0/1
+ state: down
+ provider: "{{ netconf }}"
+ ignore_errors: yes
+ register: result
+
+- assert:
+ that:
+ - "result.failed == true"
+ - "'state eq(down)' in result.failed_conditions"
+
+- debug: msg="END iosxr netconf/misc_tests.yaml on connection={{ ansible_connection }}"
diff --git a/test/integration/targets/junos_banner/tests/netconf/basic.yaml b/test/integration/targets/junos_banner/tests/netconf/basic.yaml
index f53c546..de1e0ed 100644
--- a/test/integration/targets/junos_banner/tests/netconf/basic.yaml
+++ b/test/integration/targets/junos_banner/tests/netconf/basic.yaml
@@ -78,6 +78,20 @@
- "result.changed == true"
- "'<message>this is my login banner</message>' in config.xml"
+- name: check mode
+ junos_banner:
+ banner: login
+ text: this is not the login banner you're looking for
+ state: present
+ provider: "{{ netconf }}"
+ register: result
+ check_mode: yes
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "result.failed == false"
+
- name: delete login banner
junos_banner:
banner: login
diff --git a/test/integration/targets/junos_smoke/defaults/main.yaml b/test/integration/targets/junos_smoke/defaults/main.yaml
new file mode 100644
index 0000000..9ef5ba5
--- /dev/null
+++ b/test/integration/targets/junos_smoke/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "*"
+test_items: []
diff --git a/test/integration/targets/junos_smoke/meta/main.yml b/test/integration/targets/junos_smoke/meta/main.yml
new file mode 100644
index 0000000..191a0f2
--- /dev/null
+++ b/test/integration/targets/junos_smoke/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+ - prepare_junos_tests
diff --git a/test/integration/targets/junos_smoke/tasks/main.yaml b/test/integration/targets/junos_smoke/tasks/main.yaml
new file mode 100644
index 0000000..cc27f17
--- /dev/null
+++ b/test/integration/targets/junos_smoke/tasks/main.yaml
@@ -0,0 +1,2 @@
+---
+- { include: netconf.yaml, tags: ['netconf'] }
diff --git a/test/integration/targets/junos_smoke/tasks/netconf.yaml b/test/integration/targets/junos_smoke/tasks/netconf.yaml
new file mode 100644
index 0000000..9550210
--- /dev/null
+++ b/test/integration/targets/junos_smoke/tasks/netconf.yaml
@@ -0,0 +1,21 @@
+- name: collect netconf test cases
+ find:
+ paths: "{{ role_path }}/tests/netconf"
+ patterns: "{{ testcase }}.yaml"
+ connection: local
+ register: test_cases
+
+- name: set test_items
+ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test case (connection=netconf)
+ include: "{{ test_case_to_run }} ansible_connection=netconf"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
+
+- name: run test case (connection=local)
+ include: "{{ test_case_to_run }} ansible_connection=local"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/test/integration/targets/junos_smoke/tests/netconf/common_utils.yaml b/test/integration/targets/junos_smoke/tests/netconf/common_utils.yaml
new file mode 100644
index 0000000..51c4bbc
--- /dev/null
+++ b/test/integration/targets/junos_smoke/tests/netconf/common_utils.yaml
@@ -0,0 +1,56 @@
+---
+ # junos interface -> remove_default_spec() conditional()
+ - debug: msg="START junos_interface netconf/common_utils.yaml on connection={{ ansible_connection }}"
+
+ - name: get facts
+ junos_facts:
+ provider: "{{ netconf }}"
+ register: result
+
+
+ - name: Define interface name for vSRX
+ set_fact:
+ intf_name: pp0
+ when: result['ansible_facts']['ansible_net_model'] is search("vSRX*")
+
+ - name: Define interface name for vsrx
+ set_fact:
+ intf_name: pp0
+ when: result['ansible_facts']['ansible_net_model'] is search("vsrx")
+
+ - name: Define interface name for vQFX
+ set_fact:
+ intf_name: gr-0/0/0
+ when: result['ansible_facts']['ansible_net_model'] is search("vqfx*")
+
+ - name: Check intent arguments
+ junos_interface:
+ name: "{{ intf_name }}"
+ state: up
+ tx_rate: ge(0)
+ rx_rate: le(0)
+ provider: "{{ netconf }}"
+ register: result
+
+ - assert:
+ that:
+ - "result.failed == false"
+
+ - name: Check intent arguments (failed condition)
+ junos_interface:
+ name: "{{ intf_name }}"
+ state: down
+ tx_rate: gt(0)
+ rx_rate: lt(0)
+ provider: "{{ netconf }}"
+ ignore_errors: yes
+ register: result
+
+ - assert:
+ that:
+ - "result.failed == true"
+ - "'state eq(down)' in result.failed_conditions"
+ - "'tx_rate gt(0)' in result.failed_conditions"
+ - "'rx_rate lt(0)' in result.failed_conditions"
+
+ - debug: msg="END junos_interface netconf/common_utils.yaml on connection={{ ansible_connection }}"
diff --git a/test/integration/targets/junos_smoke/tests/netconf/module_utils_junos.yaml b/test/integration/targets/junos_smoke/tests/netconf/module_utils_junos.yaml
new file mode 100644
index 0000000..9376968
--- /dev/null
+++ b/test/integration/targets/junos_smoke/tests/netconf/module_utils_junos.yaml
@@ -0,0 +1,99 @@
+---
+- debug: msg="START netconf/module_utils_junos.yaml on connection={{ ansible_connection }}"
+
+# hit get_capabilities()
+
+- name: get output for single command
+ junos_command:
+ commands: ['show version']
+ format: json
+ provider: "{{ netconf }}"
+ register: result
+
+- assert:
+ that:
+ - "result.changed == false"
+ - "result.stdout is defined"
+ - "result.stdout_lines is defined"
+
+# hit commit_configuration()
+- name: setup - remove login banner
+ junos_banner:
+ banner: login
+ state: absent
+ provider: "{{ netconf }}"
+
+- name: Create login banner
+ junos_banner:
+ banner: login
+ text: this is my login banner
+ state: present
+ provider: "{{ netconf }}"
+ register: result
+
+- name: Get running configuration
+ junos_rpc:
+ rpc: get-configuration
+ provider: "{{ netconf }}"
+ register: config
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'<message>this is my login banner</message>' in config.xml"
+
+# hit discard_changes()
+- name: check mode
+ junos_banner:
+ banner: login
+ text: this is not the banner you're looking for
+ state: present
+ provider: "{{ netconf }}"
+ register: result
+ check_mode: yes
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "result.failed == false"
+
+
+# hit field_top in map_obj_to_ele
+- name: setup - remove interface address
+ net_l3_interface:
+ name: ge-0/0/1
+ ipv4: 1.1.1.1
+ ipv6: fd5d:12c9:2201:1::1
+ state: absent
+ provider: "{{ netconf }}"
+
+- name: Configure interface address using platform agnostic module
+ net_l3_interface:
+ name: ge-0/0/1
+ ipv4: 1.1.1.1
+ ipv6: fd5d:12c9:2201:1::1
+ state: present
+ provider: "{{ netconf }}"
+ register: result
+
+- name: Get running configuration
+ junos_rpc:
+ rpc: get-configuration
+ provider: "{{ netconf }}"
+ register: config
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'<name>1.1.1.1/32</name>' in config.xml"
+ - "'<name>fd5d:12c9:2201:1::1/128</name>' in config.xml"
+ - result.diff.prepared is search("\+ *address 1.1.1.1/32")
+ - result.diff.prepared is search("\+ *address fd5d:12c9:2201:1::1/128")
+
+- name: teardown - remove interface address
+ net_l3_interface:
+ name: ge-0/0/1
+ ipv4: 1.1.1.1
+ ipv6: fd5d:12c9:2201:1::1
+ state: absent
+ provider: "{{ netconf }}"
diff --git a/test/integration/targets/nxos_aaa_server/tests/common/radius.yaml b/test/integration/targets/nxos_aaa_server/tests/common/radius.yaml
index 46e4672..517c247 100644
--- a/test/integration/targets/nxos_aaa_server/tests/common/radius.yaml
+++ b/test/integration/targets/nxos_aaa_server/tests/common/radius.yaml
@@ -73,7 +73,7 @@
- assert: *false
- name: "Remove radius server configuration"
- nxos_aaa_server:
+ nxos_aaa_server: &rad_def
server_type: radius
deadtime: default
server_timeout: default
@@ -85,6 +85,12 @@
- assert: *true
+ - name: "Check Idempotence"
+ nxos_aaa_server: *rad_def
+ register: result
+
+ - assert: *false
+
rescue:
- debug: msg="connection={{ ansible_connection }} nxos_aaa_server failure detected"
@@ -94,4 +100,4 @@
nxos_aaa_server: *remove
register: result
- - debug: msg="END connection={{ ansible_connection }} nxos_aaa_server radius.yaml sanity test"
+- debug: msg="END connection={{ ansible_connection }} nxos_aaa_server radius.yaml sanity test"
diff --git a/test/integration/targets/nxos_aaa_server/tests/common/tacacs.yaml b/test/integration/targets/nxos_aaa_server/tests/common/tacacs.yaml
index 255d9f7..0ad7eff 100644
--- a/test/integration/targets/nxos_aaa_server/tests/common/tacacs.yaml
+++ b/test/integration/targets/nxos_aaa_server/tests/common/tacacs.yaml
@@ -79,11 +79,24 @@
- assert: *false
- name: "Remove tacacs server configuration"
- nxos_aaa_server: *remove
+ nxos_aaa_server: &tac_def
+ server_type: tacacs
+ deadtime: default
+ server_timeout: default
+ global_key: default
+ directed_request: default
+ state: default
+ provider: "{{ connection }}"
register: result
- assert: *true
+ - name: "Check Idempotence"
+ nxos_aaa_server: *tac_def
+ register: result
+
+ - assert: *false
+
rescue:
- debug: msg="connection={{ ansible_connection }} nxos_aaa_server failure detected"
@@ -100,4 +113,4 @@
state: disabled
provider: "{{ connection }}"
- - debug: msg="END connection={{ ansible_connection }} nxos_aaa_server tacacs.yaml sanity test"
+- debug: msg="END connection={{ ansible_connection }} nxos_aaa_server tacacs.yaml sanity test"
diff --git a/test/integration/targets/nxos_aaa_server_host/tests/common/radius.yaml b/test/integration/targets/nxos_aaa_server_host/tests/common/radius.yaml
index 572650e..cf9a655 100644
--- a/test/integration/targets/nxos_aaa_server_host/tests/common/radius.yaml
+++ b/test/integration/targets/nxos_aaa_server_host/tests/common/radius.yaml
@@ -43,7 +43,7 @@
register: result
- assert: *false
-
+
- name: "Configure radius server non defaults"
nxos_aaa_server_host: &configure_radius_non_default
server_type: radius
@@ -54,7 +54,7 @@
state: present
provider: "{{ connection }}"
register: result
-
+
- assert: *true
- name: "Check Idempotence"
@@ -63,25 +63,38 @@
- assert: *false
- - name: "Remove radius server configuration"
- nxos_aaa_server_host: *remove
+ - name: "Configure some defaults on radius server"
+ nxos_aaa_server_host: &configure_some_radius_default
+ server_type: radius
+ address: 8.8.8.8
+ host_timeout: default
+ auth_port: 1000
+ acct_port: default
+ state: present
+ provider: "{{ connection }}"
register: result
- assert: *true
+ - name: "Check Idempotence"
+ nxos_aaa_server_host: *configure_some_radius_default
+ register: result
+
+ - assert: *false
+
- name: "Configure radius server with clear text pwd"
nxos_aaa_server_host: &configure_radius_clear_text
server_type: radius
address: 8.8.8.8
host_timeout: 25
- auth_port: 2083
+ auth_port: default
acct_port: 2084
encrypt_type: 0
key: hello
state: present
provider: "{{ connection }}"
register: result
-
+
- assert: *true
- name: "Check NOT Idempotent"
@@ -115,8 +128,49 @@
nxos_aaa_server_host: *configure_radius_type7
register: result
+ - assert: *false
+
+ - name: "Configure radius server with default key"
+ nxos_aaa_server_host: &configure_radius_defkey
+ server_type: radius
+ address: 8.8.8.8
+ host_timeout: default
+ auth_port: 1000
+ acct_port: default
+ encrypt_type: 7
+ key: default
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Check Idempotence"
+ nxos_aaa_server_host: *configure_radius_defkey
+ register: result
+
+ - assert: *false
+
+ - name: "Configure radius server with all def"
+ nxos_aaa_server_host: &configure_radius_alldef
+ server_type: radius
+ address: 8.8.8.8
+ host_timeout: default
+ auth_port: default
+ acct_port: default
+ key: default
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
- assert: *true
+ - name: "Check Idempotence"
+ nxos_aaa_server_host: *configure_radius_alldef
+ register: result
+
+ - assert: *false
+
rescue:
- debug: msg="connection={{ ansible_connection }} nxos_aaa_server_host failure detected"
@@ -127,4 +181,4 @@
nxos_aaa_server_host: *remove
register: result
- - debug: msg="END connection={{ ansible_connection }} nxos_aaa_server_host radius.yaml sanity test"
+ - debug: msg="END connection={{ ansible_connection }} nxos_aaa_server_host radius.yaml sanity test"
diff --git a/test/integration/targets/nxos_aaa_server_host/tests/common/tacacs.yaml b/test/integration/targets/nxos_aaa_server_host/tests/common/tacacs.yaml
index 4229f9a..6307316 100644
--- a/test/integration/targets/nxos_aaa_server_host/tests/common/tacacs.yaml
+++ b/test/integration/targets/nxos_aaa_server_host/tests/common/tacacs.yaml
@@ -60,7 +60,7 @@
state: present
provider: "{{ connection }}"
register: result
-
+
- assert: *true
- name: "Check Idempotence"
@@ -69,18 +69,30 @@
- assert: *false
- - name: "Remove tacacs server configuration"
- nxos_aaa_server_host: *remove
+ - name: "Configure some defaults on tacacs server"
+ nxos_aaa_server_host: &configure_some_tacacs_default
+ server_type: tacacs
+ address: 8.8.8.8
+ host_timeout: default
+ tacacs_port: 100
+ state: present
+ provider: "{{ connection }}"
register: result
- assert: *true
+ - name: "Check Idempotence"
+ nxos_aaa_server_host: *configure_some_tacacs_default
+ register: result
+
+ - assert: *false
+
- name: "Configure tacacs server with clear text pwd"
nxos_aaa_server_host: &configure_tacacs_clear_text
server_type: tacacs
address: 8.8.8.8
host_timeout: 25
- tacacs_port: 89
+ tacacs_port: default
encrypt_type: 0
key: hello
state: present
@@ -119,8 +131,47 @@
nxos_aaa_server_host: *configure_tacacs_type7
register: result
+ - assert: *false
+
+ - name: "Configure tacacs server with default key"
+ nxos_aaa_server_host: &configure_tacacs_defkey
+ server_type: tacacs
+ address: 8.8.8.8
+ host_timeout: default
+ tacacs_port: 89
+ encrypt_type: 7
+ key: default
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Check Idempotence"
+ nxos_aaa_server_host: *configure_tacacs_defkey
+ register: result
+
+ - assert: *false
+
+ - name: "Configure tacacs server with all def"
+ nxos_aaa_server_host: &configure_tacacs_alldef
+ server_type: tacacs
+ address: 8.8.8.8
+ host_timeout: default
+ tacacs_port: default
+ key: default
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
- assert: *true
+ - name: "Check Idempotence"
+ nxos_aaa_server_host: *configure_tacacs_alldef
+ register: result
+
+ - assert: *false
+
rescue:
- debug: msg="connection={{ ansible_connection }} nxos_aaa_server_host failure detected"
@@ -131,7 +182,7 @@
nxos_aaa_server_host: *remove
register: result
- - name: "Enable feature tacacs+"
+ - name: "Disable feature tacacs+"
nxos_feature:
feature: tacacs+
state: disabled
diff --git a/test/integration/targets/nxos_acl/tests/common/sanity.yaml b/test/integration/targets/nxos_acl/tests/common/sanity.yaml
index 1bf3cb9..83d21ab 100644
--- a/test/integration/targets/nxos_acl/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_acl/tests/common/sanity.yaml
@@ -10,12 +10,12 @@
nxos_acl: &remove
name: TEST_ACL
seq: 10
- state: absent
+ state: delete_acl
provider: "{{ connection }}"
ignore_errors: yes
-- name: "Configure ACL"
- nxos_acl: &configure
+- name: "Configure ACE10"
+ nxos_acl: &conf10
name: TEST_ACL
seq: 10
action: permit
@@ -27,6 +27,8 @@
ack: 'enable'
dscp: 'af43'
dest: any
+ dest_port_op: neq
+ dest_port1: 1899
urg: 'enable'
psh: 'enable'
established: 'enable'
@@ -44,13 +46,187 @@
- "result.changed == true"
- name: "Check Idempotence"
- nxos_acl: *configure
+ nxos_acl: *conf10
register: result
- assert: &false
that:
- "result.changed == false"
+- name: "Change ACE10"
+ nxos_acl: &chg10
+ name: TEST_ACL
+ seq: 10
+ action: deny
+ proto: tcp
+ src: 1.1.1.1/24
+ src_port_op: range
+ src_port1: 1900
+ src_port2: 1910
+ ack: 'enable'
+ dscp: 'af43'
+ dest: any
+ dest_port_op: neq
+ dest_port1: 1899
+ urg: 'enable'
+ psh: 'enable'
+ established: 'enable'
+ log: 'enable'
+ fin: 'enable'
+ rst: 'enable'
+ syn: 'enable'
+ time_range: "{{time_range|default(omit)}}"
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+ nxos_acl: *chg10
+ register: result
+
+- assert: *false
+
+- name: "ace remark"
+ nxos_acl: &remark
+ name: TEST_ACL
+ seq: 20
+ action: remark
+ remark: test_remark
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+ nxos_acl: *remark
+ register: result
+
+- assert: *false
+
+- name: "change remark"
+ nxos_acl: &chgremark
+ name: TEST_ACL
+ seq: 20
+ action: remark
+ remark: changed_remark
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+ nxos_acl: *chgremark
+ register: result
+
+- assert: *false
+
+- name: "ace 30"
+ nxos_acl: &ace30
+ name: TEST_ACL
+ seq: 30
+ action: deny
+ proto: 24
+ src: any
+ dest: any
+ fragments: enable
+ precedence: network
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+ nxos_acl: *ace30
+ register: result
+
+- assert: *false
+
+- name: "change ace 30 options"
+ nxos_acl: &chgace30opt
+ name: TEST_ACL
+ seq: 30
+ action: deny
+ proto: 24
+ src: any
+ dest: any
+ precedence: network
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+ nxos_acl: *chgace30opt
+ register: result
+
+- assert: *false
+
+- name: "ace 40"
+ nxos_acl: &ace40
+ name: TEST_ACL
+ seq: 40
+ action: permit
+ proto: udp
+ src: any
+ src_port_op: neq
+ src_port1: 1200
+ dest: any
+ precedence: network
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+ nxos_acl: *ace40
+ register: result
+
+- assert: *false
+
+- name: "change ace 40"
+ nxos_acl: &chgace40
+ name: TEST_ACL
+ seq: 40
+ action: permit
+ proto: udp
+ src: any
+ dest: any
+ precedence: network
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+ nxos_acl: *chgace40
+ register: result
+
+- assert: *false
+
+- name: "remove ace 30"
+ nxos_acl: &remace30
+ name: TEST_ACL
+ seq: 30
+ state: absent
+ provider: "{{ connection }}"
+ register: result
+
+- assert: *true
+
+- name: "Check Idempotence"
+ nxos_acl: *remace30
+ register: result
+
+- assert: *false
+
- name: "Remove ACL"
nxos_acl: *remove
register: result
diff --git a/test/integration/targets/nxos_acl_interface/tests/common/sanity.yaml b/test/integration/targets/nxos_acl_interface/tests/common/sanity.yaml
index 8d463d3..f397c2c 100644
--- a/test/integration/targets/nxos_acl_interface/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_acl_interface/tests/common/sanity.yaml
@@ -16,11 +16,21 @@
provider: "{{ connection }}"
ignore_errors: yes
+- name: "Setup: Put interface into no switch port mode"
+ nxos_config:
+ commands:
+ - "no switchport"
+ parents:
+ - "interface {{ intname }}"
+ match: none
+ provider: "{{ connection }}"
+ ignore_errors: yes
+
- name: "Setup: Cleanup possibly existing acl"
nxos_acl: &remove
name: ANSIBLE_ACL
seq: 10
- state: absent
+ state: delete_acl
provider: "{{ connection }}"
ignore_errors: yes
@@ -112,9 +122,9 @@
nxos_config: *default
ignore_errors: yes
+ always:
- name: Remove possible configured ACL
nxos_acl: *remove
ignore_errors: yes
- always:
- debug: msg="END connection={{ ansible_connection }} nxos_acl_interface sanity test"
diff --git a/test/integration/targets/nxos_hsrp/tests/cli/sanity.yaml b/test/integration/targets/nxos_hsrp/tests/cli/sanity.yaml
deleted file mode 100644
index 413b3df..0000000
--- a/test/integration/targets/nxos_hsrp/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: targets/nxos_hsrp/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_hsrp/tests/common/sanity.yaml b/test/integration/targets/nxos_hsrp/tests/common/sanity.yaml
index 0b7cc58..365fbe7 100644
--- a/test/integration/targets/nxos_hsrp/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_hsrp/tests/common/sanity.yaml
@@ -4,7 +4,8 @@
when: ansible_connection == "local"
# Select interface for test
-- set_fact: intname="{{ nxos_int1 }}"
+- set_fact: intname1="{{ nxos_int1 }}"
+- set_fact: intname2="{{ nxos_int2 }}"
- block:
- name: "Enable feature hsrp"
@@ -13,25 +14,34 @@
state: enabled
provider: "{{ connection }}"
- - name: "change interface mode"
+ - name: "change int1 mode"
nxos_config:
commands:
- no switchport
parents:
- - "interface {{ intname }}"
+ - "interface {{ intname1 }}"
+ match: none
+ provider: "{{ connection }}"
+
+ - name: "change int2 mode"
+ nxos_config:
+ commands:
+ - no switchport
+ parents:
+ - "interface {{ intname2 }}"
match: none
provider: "{{ connection }}"
- name: "configure nxos_hsrp"
- nxos_hsrp: &configure
- group: 10
+ nxos_hsrp: &conf1000
+ group: 1000
version: 2
vip: 10.1.1.1
priority: 150
- interface: "{{ intname }}"
+ interface: "{{ intname1 }}"
preempt: enabled
- auth_type: text
- auth_string: CISCO
+ auth_type: md5
+ auth_string: "7 1234"
provider: "{{ connection }}"
register: result
@@ -40,50 +50,109 @@
- "result.changed == true"
- name: "Conf Idempotence"
- nxos_hsrp: *configure
+ nxos_hsrp: *conf1000
register: result
- assert: &false
that:
- "result.changed == false"
- - name: "remove nxos_hsrp"
- nxos_hsrp: &remove
- group: 10
+ - name: "configure group 100"
+ nxos_hsrp: &conf100
+ group: 100
version: 2
- vip: 10.1.1.1
- priority: 150
- interface: "{{ intname }}"
+ vip: 2.2.2.2
+ priority: 25
+ interface: "{{ intname1 }}"
preempt: enabled
+ auth_type: md5
+ auth_string: "0 1234"
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Conf Idempotence"
+ nxos_hsrp: *conf100
+ register: result
+
+ - assert: *false
+
+ - name: "change group 100"
+ nxos_hsrp: &chg100
+ group: 100
+ version: 2
+ vip: default
+ priority: default
+ interface: "{{ intname1 }}"
+ preempt: disabled
+ auth_type: md5
+ auth_string: "0 1234"
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Conf Idempotence"
+ nxos_hsrp: *chg100
+ register: result
+
+ - assert: *false
+
+ - name: "configure group 200"
+ nxos_hsrp: &conf200
+ group: 200
+ vip: 3.3.3.3
+ version: 1
+ interface: "{{ intname2 }}"
auth_type: text
- auth_string: CISCO
+ auth_string: "1234"
provider: "{{ connection }}"
- state: absent
register: result
- assert: *true
- - name: "Remove Idempotence"
- nxos_hsrp: *remove
+ - name: "Conf Idempotence"
+ nxos_hsrp: *conf200
register: result
- assert: *false
- always:
- - name: "remove nxos_hsrp"
- nxos_hsrp:
- group: 10
+ - name: "change group 200"
+ nxos_hsrp: &chg200
+ group: 200
+ vip: 3.3.3.3
version: 2
- vip: 10.1.1.1
- priority: 150
- interface: "{{ intname }}"
- preempt: enabled
+ interface: "{{ intname2 }}"
auth_type: text
- auth_string: CISCO
+ auth_string: default
provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Conf Idempotence"
+ nxos_hsrp: *chg200
+ register: result
+
+ - assert: *false
+
+ - name: "remove nxos_hsrp"
+ nxos_hsrp: &remove
+ group: 1000
+ interface: "{{ intname1 }}"
state: absent
- ignore_errors: yes
+ register: result
+
+ - assert: *true
+
+ - name: "Remove Idempotence"
+ nxos_hsrp: *remove
+ register: result
+
+ - assert: *false
+ always:
- name: "Disable feature hsrp"
nxos_feature:
feature: hsrp
diff --git a/test/integration/targets/nxos_hsrp/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_hsrp/tests/nxapi/sanity.yaml
deleted file mode 100644
index 7718b7c..0000000
--- a/test/integration/targets/nxos_hsrp/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: targets/nxos_hsrp/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_igmp/tests/common/sanity.yaml b/test/integration/targets/nxos_igmp/tests/common/sanity.yaml
index da8bce3..5689d99 100644
--- a/test/integration/targets/nxos_igmp/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_igmp/tests/common/sanity.yaml
@@ -3,6 +3,9 @@
- debug: msg="Using provider={{ connection.transport }}"
when: ansible_connection == "local"
+- set_fact: restart="true"
+ when: platform is not match("N35")
+
- block:
- name: Configure igmp with non-default values
@@ -26,23 +29,45 @@
that:
- "result.changed == false"
- - name: Configure igmp with default values
+ - name: Configure igmp defaults
nxos_igmp: &default
- state: default
+ flush_routes: false
+ enforce_rtr_alert: false
+ restart: "{{restart|default(omit)}}"
+ state: present
provider: "{{ connection }}"
register: result
- assert: *true
- - name: "Check Idempotence - Configure igmp with default values"
+ - name: "Check Idempotence - Configure igmp with defaults"
nxos_igmp: *default
register: result
- assert: *false
+ - name: Configure igmp non-defaults again
+ nxos_igmp: *non-default
+ register: result
+
+ - name: Configure igmp state as values
+ nxos_igmp: &sdefault
+ state: default
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Check Idempotence - Configure igmp with state default"
+ nxos_igmp: *sdefault
+ register: result
+
+ - assert: *false
+
always:
- name: Configure igmp with default values
- nxos_igmp: *default
+ nxos_igmp: *sdefault
register: result
+ ignore_errors: yes
- debug: msg="END connection={{ ansible_connection }} nxos_igmp sanity test"
diff --git a/test/integration/targets/nxos_igmp_snooping/tests/common/sanity.yaml b/test/integration/targets/nxos_igmp_snooping/tests/common/sanity.yaml
index 9d54832..21940b7 100644
--- a/test/integration/targets/nxos_igmp_snooping/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_igmp_snooping/tests/common/sanity.yaml
@@ -3,14 +3,24 @@
- debug: msg="Using provider={{ connection.transport }}"
when: ansible_connection == "local"
+- set_fact: gt_run="false"
+- set_fact: gt_run="true"
+ when: not (platform is match("N5K")) and not (platform is match("N35"))
+
+- set_fact: group_timeout="never"
+ when: not (platform is match("N5K")) and not (platform is match("N35"))
+
+- set_fact: def_group_timeout="default"
+ when: not (platform is match("N5K")) and not (platform is match("N35"))
+
- block:
- name: Configure igmp snooping with non-default values
nxos_igmp_snooping: &non-default
- snooping: true
- group_timeout: never
- link_local_grp_supp: true
- report_supp: true
+ snooping: false
+ group_timeout: "{{group_timeout|default(omit)}}"
+ link_local_grp_supp: false
+ report_supp: false
v3_report_supp: true
state: present
provider: "{{ connection }}"
@@ -19,14 +29,33 @@
- assert: &true
that:
- "result.changed == true"
+ - block:
+ - name: "Check Idempotence - Configure igmp snooping with non-default values"
+ nxos_igmp_snooping: *non-default
+ register: result
- - name: "Check Idempotence - Configure igmp snooping with non-default values"
- nxos_igmp_snooping: *non-default
+ - assert: &false
+ that:
+ - "result.changed == false"
+ when: (imagetag and (imagetag is version_compare('D1', 'ne')))
+
+ - name: Configure igmp snooping with default group timeout
+ nxos_igmp_snooping: &defgt
+ group_timeout: "{{def_group_timeout|default(omit)}}"
+ state: present
+ provider: "{{ connection }}"
register: result
- - assert: &false
- that:
- - "result.changed == false"
+ - assert: *true
+ when: gt_run
+
+ - block:
+ - name: "Check Idempotence"
+ nxos_igmp_snooping: *defgt
+ register: result
+
+ - assert: *false
+ when: gt_run or (imagetag and (imagetag is version_compare('D1', 'ne')))
- name: Configure igmp snooping with default values
nxos_igmp_snooping: &default
@@ -36,11 +65,13 @@
- assert: *true
- - name: "Check Idempotence - Configure igmp snooping with default values"
- nxos_igmp_snooping: *default
- register: result
+ - block:
+ - name: "Check Idempotence - Configure igmp snooping with default values"
+ nxos_igmp_snooping: *default
+ register: result
- - assert: *false
+ - assert: *false
+ when: (imagetag and (imagetag is version_compare('D1', 'ne')))
always:
- name: Configure igmp snooping with default values
diff --git a/test/integration/targets/nxos_l2_interface/tests/common/sanity.yaml b/test/integration/targets/nxos_l2_interface/tests/common/sanity.yaml
index 1558eba..16dc760 100644
--- a/test/integration/targets/nxos_l2_interface/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_l2_interface/tests/common/sanity.yaml
@@ -20,6 +20,7 @@
nxos_interface:
interface: "{{ intname }}"
mode: layer2
+ provider: "{{ connection }}"
- name: "Setup vlans"
nxos_vlan:
@@ -91,7 +92,7 @@
- assert: *false
- - name: Ensure these VLANs are not being tagged on the trunk
+ - name: Remove full trunk vlan range 2-50
nxos_l2_interface: &no_tag
name: "{{ intname }}"
mode: trunk
@@ -102,12 +103,41 @@
- assert: *true
- - name: "no tag vlan Idempotence"
+ - name: Check Idempotence Remove full trunk vlan range 2-50
nxos_l2_interface: *no_tag
register: result
- assert: *false
+ - name: Reconfigure interface trunk port and ensure 2-50 are being tagged
+ nxos_l2_interface: *tag
+ register: result
+
+ - assert: *true
+
+ - name: Check Idempotence Reconfigure interface trunk port and ensure 2-50 are being tagged
+ nxos_l2_interface: *tag
+ register: result
+
+ - assert: *false
+
+ - name: Remove partial trunk vlan range 30-4094 are removed
+ nxos_l2_interface: &partial
+ name: "{{ intname }}"
+ mode: trunk
+ trunk_vlans: 30-4094
+ state: absent
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: Check Idempotence Remove partial trunk vlan range 30-4094 are removed
+ nxos_l2_interface: *partial
+ register: result
+
+ - assert: *false
+
- name: put interface default state
nxos_l2_interface: *def_swi
register: result
diff --git a/test/integration/targets/nxos_ntp/tests/cli/sanity.yaml b/test/integration/targets/nxos_ntp/tests/cli/sanity.yaml
deleted file mode 100644
index c28f9d6..0000000
--- a/test/integration/targets/nxos_ntp/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: targets/nxos_ntp/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_ntp/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_ntp/tests/nxapi/sanity.yaml
deleted file mode 100644
index 7e6a8bd..0000000
--- a/test/integration/targets/nxos_ntp/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: targets/nxos_ntp/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_ntp_auth/tests/common/sanity.yaml b/test/integration/targets/nxos_ntp_auth/tests/common/sanity.yaml
index e9e411d..76f1ea5 100644
--- a/test/integration/targets/nxos_ntp_auth/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_ntp_auth/tests/common/sanity.yaml
@@ -17,9 +17,7 @@
nxos_ntp_auth: &configure_text
key_id: 32
md5string: hello
- auth_type: text
- trusted_key: true
- authentication: on
+ authentication: off
state: present
provider: "{{ connection }}"
register: result
@@ -28,21 +26,11 @@
that:
- "result.changed == true"
- - name: "Check Idempotence - Configure text ntp authentication"
- nxos_ntp_auth: *configure_text
- register: result
-
- - assert: &false
- that:
- - "result.changed == false"
-
- name: Remove text ntp authentication
nxos_ntp_auth: &remove_text
key_id: 32
md5string: hello
- auth_type: text
- trusted_key: true
- authentication: on
+ authentication: off
state: absent
provider: "{{ connection }}"
register: result
@@ -54,8 +42,6 @@
key_id: 32
md5string: hello
auth_type: encrypt
- trusted_key: true
- authentication: on
state: present
provider: "{{ connection }}"
register: result
@@ -66,6 +52,70 @@
nxos_ntp_auth: *configure_encrypt
register: result
+ - assert: &false
+ that:
+ - "result.changed == false"
+
+ - name: Turn on authentication
+ nxos_ntp_auth: &authon
+ authentication: on
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Check Idempotence - Turn on authentication"
+ nxos_ntp_auth: *authon
+ register: result
+
+ - assert: *false
+
+ - name: Turn off authentication
+ nxos_ntp_auth: &authoff
+ authentication: off
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Check Idempotence - Turn off authentication"
+ nxos_ntp_auth: *authoff
+ register: result
+
+ - assert: *false
+
+ - name: Add trusted key
+ nxos_ntp_auth: &tkey
+ key_id: 32
+ trusted_key: true
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Check Idempotence - Add trusted key"
+ nxos_ntp_auth: *tkey
+ register: result
+
+ - assert: *false
+
+ - name: Remove trusted key
+ nxos_ntp_auth: &rtkey
+ key_id: 32
+ trusted_key: false
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Check Idempotence - Remove trusted key"
+ nxos_ntp_auth: *rtkey
+ register: result
+
- assert: *false
- name: Remove encrypt ntp authentication
@@ -73,7 +123,6 @@
key_id: 32
md5string: hello
auth_type: encrypt
- trusted_key: true
authentication: on
state: absent
provider: "{{ connection }}"
@@ -81,6 +130,12 @@
- assert: *true
+ - name: "Check Idempotence - Remove encrypt ntp authentication"
+ nxos_ntp_auth: *remove_encrypt
+ register: result
+
+ - assert: *false
+
always:
- name: Cleanup ntp auth config
nxos_ntp_auth: *setup
diff --git a/test/integration/targets/nxos_ntp_options/tests/common/sanity.yaml b/test/integration/targets/nxos_ntp_options/tests/common/sanity.yaml
index 43833de..fd64136 100644
--- a/test/integration/targets/nxos_ntp_options/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_ntp_options/tests/common/sanity.yaml
@@ -31,43 +31,73 @@
that:
- "result.changed == false"
- - name: Remove ntp with master and default stratum
- nxos_ntp_options: &remove_master_default_stratum
- logging: true
+ - name: Configure ntp with master and non-default stratum
+ nxos_ntp_options: &configure_master_non_default_stratum
master: true
- state: absent
+ stratum: 10
+ state: present
provider: "{{ connection }}"
register: result
- assert: *true
- - name: Configure ntp with master and non-default stratum
- nxos_ntp_options: &configure_master_non_default_stratum
+ - name: "Check Idempotence - Configure ntp with master and non-default stratum"
+ nxos_ntp_options: *configure_master_non_default_stratum
+ register: result
+
+ - assert: *false
+
+ - name: Configure ntp with master and no logging
+ nxos_ntp_options: &configure_no_log
master: true
- logging: true
stratum: 10
+ logging: false
state: present
- provider: "{{ connection }}"
+ provider: "{{ connection }}"
register: result
- assert: *true
- - name: "Check Idempotence - Configure ntp with master and non-default stratum"
- nxos_ntp_options: *configure_master_non_default_stratum
+ - name: "Check Idempotence - Configure ntp with master and no logging"
+ nxos_ntp_options: *configure_no_log
register: result
- assert: *false
- - name: Remove ntp with master and non-default stratum
- nxos_ntp_options: &remove_master_non_default_stratum
+ - name: Configure ntp with logging and no master
+ nxos_ntp_options: &configure_no_master
+ master: false
logging: true
- master: true
- state: absent
+ state: present
provider: "{{ connection }}"
register: result
- assert: *true
+ - name: "Check Idempotence - Configure ntp with logging and no master"
+ nxos_ntp_options: *configure_no_master
+ register: result
+
+ - assert: *false
+
+ - name: "Configure ntp with master and non-default stratum again"
+ nxos_ntp_options: *configure_master_non_default_stratum
+ register: result
+
+ - assert: *true
+
+ - name: Remove ntp options
+ nxos_ntp_options: *default
+ register: result
+
+ - assert: *true
+
+ - name: "Check Idempotence - Remove"
+ nxos_ntp_options: *default
+ register: result
+
+ - assert: *false
+
always:
- name: Cleanup ntp config
nxos_ntp_options: *default
diff --git a/test/integration/targets/nxos_ospf_vrf/tests/cli/sanity.yaml b/test/integration/targets/nxos_ospf_vrf/tests/cli/sanity.yaml
deleted file mode 100644
index 5710969..0000000
--- a/test/integration/targets/nxos_ospf_vrf/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: targets/nxos_ospf_vrf/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_ospf_vrf/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_ospf_vrf/tests/nxapi/sanity.yaml
deleted file mode 100644
index 1c3f845..0000000
--- a/test/integration/targets/nxos_ospf_vrf/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: targets/nxos_ospf_vrf/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_overlay_global/tests/cli/sanity.yaml b/test/integration/targets/nxos_overlay_global/tests/cli/sanity.yaml
deleted file mode 100644
index 420af74..0000000
--- a/test/integration/targets/nxos_overlay_global/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: "{{ role_path }}/tests/common/sanity.yaml"
diff --git a/test/integration/targets/nxos_overlay_global/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_overlay_global/tests/nxapi/sanity.yaml
deleted file mode 100644
index e30ea6e..0000000
--- a/test/integration/targets/nxos_overlay_global/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: "{{ role_path }}/tests/common/sanity.yaml"
diff --git a/test/integration/targets/nxos_pim_interface/tests/cli/sanity.yaml b/test/integration/targets/nxos_pim_interface/tests/cli/sanity.yaml
deleted file mode 100644
index 420af74..0000000
--- a/test/integration/targets/nxos_pim_interface/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: "{{ role_path }}/tests/common/sanity.yaml"
diff --git a/test/integration/targets/nxos_pim_interface/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_pim_interface/tests/nxapi/sanity.yaml
deleted file mode 100644
index e30ea6e..0000000
--- a/test/integration/targets/nxos_pim_interface/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: "{{ role_path }}/tests/common/sanity.yaml"
diff --git a/test/integration/targets/nxos_smoke/defaults/main.yaml b/test/integration/targets/nxos_smoke/defaults/main.yaml
new file mode 100644
index 0000000..9ef5ba5
--- /dev/null
+++ b/test/integration/targets/nxos_smoke/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "*"
+test_items: []
diff --git a/test/integration/targets/nxos_smoke/meta/main.yml b/test/integration/targets/nxos_smoke/meta/main.yml
new file mode 100644
index 0000000..ae741cb
--- /dev/null
+++ b/test/integration/targets/nxos_smoke/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+ - prepare_nxos_tests
diff --git a/test/integration/targets/nxos_smoke/tasks/cli.yaml b/test/integration/targets/nxos_smoke/tasks/cli.yaml
new file mode 100644
index 0000000..edbff7d
--- /dev/null
+++ b/test/integration/targets/nxos_smoke/tasks/cli.yaml
@@ -0,0 +1,33 @@
+---
+- name: collect common cli test cases
+ find:
+ paths: "{{ role_path }}/tests/common"
+ patterns: "{{ testcase }}.yaml"
+ connection: local
+ register: test_cases
+
+- name: collect cli test cases
+ find:
+ paths: "{{ role_path }}/tests/cli"
+ patterns: "{{ testcase }}.yaml"
+ connection: local
+ register: cli_cases
+
+- set_fact:
+ test_cases:
+ files: "{{ test_cases.files }} + {{ cli_cases.files }}"
+
+- name: set test_items
+ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test cases (connection=network_cli)
+ include: "{{ test_case_to_run }} ansible_connection=network_cli connection={}"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
+
+- name: run test case (connection=local)
+ include: "{{ test_case_to_run }} ansible_connection=local connection={{ cli }}"
+ with_first_found: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/test/integration/targets/nxos_smoke/tasks/main.yaml b/test/integration/targets/nxos_smoke/tasks/main.yaml
new file mode 100644
index 0000000..4b0f8c6
--- /dev/null
+++ b/test/integration/targets/nxos_smoke/tasks/main.yaml
@@ -0,0 +1,3 @@
+---
+- { include: cli.yaml, tags: ['cli'] }
+- { include: nxapi.yaml, tags: ['nxapi'] }
diff --git a/test/integration/targets/nxos_smoke/tasks/nxapi.yaml b/test/integration/targets/nxos_smoke/tasks/nxapi.yaml
new file mode 100644
index 0000000..68e96a2
--- /dev/null
+++ b/test/integration/targets/nxos_smoke/tasks/nxapi.yaml
@@ -0,0 +1,27 @@
+---
+- name: collect common nxapi test cases
+ find:
+ paths: "{{ role_path }}/tests/common"
+ patterns: "{{ testcase }}.yaml"
+ connection: local
+ register: test_cases
+
+- name: collect nxapi test cases
+ find:
+ paths: "{{ role_path }}/tests/nxapi"
+ patterns: "{{ testcase }}.yaml"
+ connection: local
+ register: nxapi_cases
+
+- set_fact:
+ test_cases:
+ files: "{{ test_cases.files }} + {{ nxapi_cases.files }}"
+
+- name: set test_items
+ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test cases (connection=local)
+ include: "{{ test_case_to_run }} ansible_connection=local connection={{ nxapi }}"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/test/integration/targets/nxos_smoke/tests/common/common_config.yaml b/test/integration/targets/nxos_smoke/tests/common/common_config.yaml
new file mode 100644
index 0000000..fe58b73
--- /dev/null
+++ b/test/integration/targets/nxos_smoke/tests/common/common_config.yaml
@@ -0,0 +1,160 @@
+---
+# nxos_config -> NetworkConfig, dumps
+# nxos_static_route -> CustomNetworkConfig
+
+# hit NetworkConfig
+# Select interface for test
+- debug: msg="START connection={{ ansible_connection }} common/common_config.yaml"
+- debug: msg="Using provider={{ connection.transport }}"
+ when: ansible_connection == "local"
+
+- set_fact: intname="{{ nxos_int1 }}"
+
+- name: setup
+ nxos_config:
+ commands:
+ - no description
+ - no shutdown
+ parents:
+ - "interface {{ intname }}"
+ match: none
+ provider: "{{ connection }}"
+
+- name: collect any backup files
+ find: &backups
+ paths: "{{ role_path }}/backup"
+ pattern: "{{ inventory_hostname_short }}_config*"
+ connection: local
+ register: backup_files
+
+- name: delete backup files
+ file:
+ path: "{{ item.path }}"
+ state: absent
+ with_items: "{{backup_files.files|default([])}}"
+
+- name: configure device with config
+ nxos_config:
+ commands:
+ - description this is a test
+ - shutdown
+ parents:
+ - "interface {{ intname }}"
+ backup: yes
+ provider: "{{ connection }}"
+ register: result
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "result.updates is defined"
+
+- name: collect any backup files
+ find: *backups
+ connection: local
+ register: backup_files
+
+- assert:
+ that:
+ - "backup_files.files is defined"
+
+# hit block/sublevel sections
+- name: setup
+ nxos_config: &clear
+ lines: no ip access-list test
+ provider: "{{ connection }}"
+ match: none
+ ignore_errors: yes
+
+# hit NetworkConfig._diff_exact
+- name: configure sub level command using block replace - exact
+ nxos_config:
+ lines:
+ - 10 permit ip 1.1.1.1/32 any log
+ - 20 permit ip 2.2.2.2/32 any log
+ - 30 permit ip 3.3.3.3/32 any log
+ - 40 permit ip 4.4.4.4/32 any log
+ parents: ip access-list test
+ replace: block
+ provider: "{{ connection }}"
+ match: exact
+ register: result
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'ip access-list test' in result.updates"
+ - "'10 permit ip 1.1.1.1/32 any log' in result.updates"
+ - "'20 permit ip 2.2.2.2/32 any log' in result.updates"
+ - "'30 permit ip 3.3.3.3/32 any log' in result.updates"
+ - "'40 permit ip 4.4.4.4/32 any log' in result.updates"
+
+# hit NetworkConfig._diff_strict
+- name: configure sub level command using block replace strict
+ nxos_config:
+ lines:
+ - 10 permit ip 1.1.1.1/32 any log
+ - 20 permit ip 2.2.2.2/32 any log
+ - 30 permit ip 3.3.3.3/32 any log
+ - 40 permit ip 4.4.4.4/32 any log
+ parents: ip access-list test
+ replace: block
+ provider: "{{ connection }}"
+ match: strict
+ register: result
+
+- name: teardown
+ nxos_config: *clear
+
+# hit CustomNetworkConfig
+- block:
+ - name: create static route
+ nxos_static_route: &configure
+ prefix: "192.168.20.64/24"
+ next_hop: "3.3.3.3"
+ route_name: testing
+ pref: 100
+ tag: 5500
+ vrf: testing
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: &true
+ that:
+ - "result.changed == true"
+
+ - name: remove static route
+ nxos_static_route: &remove
+ prefix: "192.168.20.64/24"
+ next_hop: "3.3.3.3"
+ route_name: testing
+ pref: 100
+ tag: 5500
+ vrf: testing
+ state: absent
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ always:
+ - name: remove static route
+ nxos_static_route:
+ prefix: "192.168.20.64/24"
+ next_hop: "3.3.3.3"
+ route_name: testing
+ pref: 100
+ tag: 5500
+ vrf: testing
+ state: absent
+ provider: "{{ connection }}"
+ ignore_errors: yes
+
+ - name: remove static route aggregate
+ nxos_static_route:
+ aggregate:
+ - { prefix: "192.168.22.64/24", next_hop: "3.3.3.3" }
+ - { prefix: "192.168.24.64/24", next_hop: "3.3.3.3" }
+ state: absent
+ provider: "{{ connection }}"
+ ignore_errors: yes
diff --git a/test/integration/targets/nxos_smoke/tests/common/common_utils.yaml b/test/integration/targets/nxos_smoke/tests/common/common_utils.yaml
new file mode 100644
index 0000000..df8dd1d
--- /dev/null
+++ b/test/integration/targets/nxos_smoke/tests/common/common_utils.yaml
@@ -0,0 +1,101 @@
+---
+# nxos_command -> ComplexList
+# nxos_config -> to_list
+# nxos_interface -> conditional, remove_default_spec
+
+- debug: msg="START connection={{ ansible_connection }} common/common_utils.yaml"
+- debug: msg="Using provider={{ connection.transport }}"
+ when: ansible_connection == "local"
+
+# hit ComplexList
+- name: test contains operator
+ nxos_command:
+ commands:
+ - show version
+
+# hit to_list()
+- name: setup
+ nxos_config:
+ lines: hostname switch
+ provider: "{{ connection }}"
+ match: none
+
+- name: configure top level command
+ nxos_config:
+ lines: hostname foo
+ provider: "{{ connection }}"
+ register: result
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'hostname foo' in result.updates"
+
+- name: setup
+ nxos_config:
+ lines: hostname switch
+ provider: "{{ connection }}"
+ match: none
+
+# hit conditional()
+- set_fact: testint1="{{ nxos_int1 }}"
+- set_fact: testint2="{{ nxos_int2 }}"
+
+- name: "Setup: Put interfaces into a default state"
+ nxos_config:
+ lines:
+ - "default interface {{ testint1 }}"
+ - "default interface {{ testint2 }}"
+ provider: "{{ connection }}"
+ ignore_errors: yes
+
+ register: result
+
+- name: Check intent arguments
+ nxos_interface:
+ name: "{{ testint2 }}"
+ admin_state: up
+ tx_rate: ge(0)
+ rx_rate: ge(0)
+ provider: "{{ connection }}"
+ register: result
+
+- assert:
+ that:
+ - "result.failed == false"
+
+- name: Check intent arguments (failed condition)
+ nxos_interface:
+ name: "{{ testint2 }}"
+ admin_state: down
+ tx_rate: gt(0)
+ rx_rate: lt(0)
+ provider: "{{ connection }}"
+ ignore_errors: yes
+ register: result
+
+- assert:
+ that:
+ - "result.failed == true"
+ - "'tx_rate gt(0)' in result.failed_conditions"
+ - "'rx_rate lt(0)' in result.failed_conditions"
+
+- name: aggregate definition of interface
+ nxos_interface:
+ aggregate:
+ - { name: "{{ testint1 }}", description: "Test aggregation on first interface" }
+ - { name: "{{ testint2 }}", mode: layer3 }
+ provider: "{{ connection }}"
+ register: result
+
+- assert:
+ that:
+ - "result.changed == true"
+
+- name: "TearDown: Put interfaces into a default state"
+ nxos_config:
+ lines:
+ - "default interface {{ testint1 }}"
+ - "default interface {{ testint2 }}"
+ provider: "{{ connection }}"
+ ignore_errors: yes
diff --git a/test/integration/targets/nxos_smoke/tests/common/misc_tests.yaml b/test/integration/targets/nxos_smoke/tests/common/misc_tests.yaml
new file mode 100644
index 0000000..27fc07a
--- /dev/null
+++ b/test/integration/targets/nxos_smoke/tests/common/misc_tests.yaml
@@ -0,0 +1,24 @@
+---
+- debug: msg="START connection={{ ansible_connection }} common/misc_tests.yaml"
+- debug: msg="Using provider={{ connection.transport }}"
+ when: ansible_connection == "local"
+
+- name: hit conditional for lists of 10 or more commands
+ nxos_command:
+ commands:
+ - show hostname
+ - show hostname
+ - show hostname
+ - show hostname
+ - show hostname
+ - show hostname
+ - show hostname
+ - show hostname
+ - show hostname
+ - show hostname
+ provider: "{{ connection }}"
+ register: result
+
+- assert:
+ that:
+ - result.stdout|length == 10
diff --git a/test/integration/targets/nxos_snapshot/tests/cli/sanity.yaml b/test/integration/targets/nxos_snapshot/tests/cli/sanity.yaml
deleted file mode 100644
index b73edf2..0000000
--- a/test/integration/targets/nxos_snapshot/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: targets/nxos_snapshot/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_snapshot/tests/common/sanity.yaml b/test/integration/targets/nxos_snapshot/tests/common/sanity.yaml
index 8a5de08..4f1e18f 100644
--- a/test/integration/targets/nxos_snapshot/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_snapshot/tests/common/sanity.yaml
@@ -36,6 +36,22 @@
path: '.'
provider: "{{ connection }}"
+ - name: FAIL compare snapshots
+ nxos_snapshot:
+ action: compare
+ snapshot1: test_snapshot1
+ snapshot2: test_snapshot2
+ compare_option: summary
+ path: '.'
+ provider: "{{ connection }}"
+ register: result
+ ignore_errors: yes
+
+ - assert:
+ that:
+ - 'result.failed == True'
+ - '"action is compare but all of the following are missing: comparison_results_file" in result.msg'
+
when: snapshot_run
always:
diff --git a/test/integration/targets/nxos_snapshot/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_snapshot/tests/nxapi/sanity.yaml
deleted file mode 100644
index a13de83..0000000
--- a/test/integration/targets/nxos_snapshot/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: targets/nxos_snapshot/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_snmp_community/tests/cli/sanity.yaml b/test/integration/targets/nxos_snmp_community/tests/cli/sanity.yaml
deleted file mode 100644
index 420af74..0000000
--- a/test/integration/targets/nxos_snmp_community/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: "{{ role_path }}/tests/common/sanity.yaml"
diff --git a/test/integration/targets/nxos_snmp_community/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_snmp_community/tests/nxapi/sanity.yaml
deleted file mode 100644
index e30ea6e..0000000
--- a/test/integration/targets/nxos_snmp_community/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: "{{ role_path }}/tests/common/sanity.yaml"
diff --git a/test/integration/targets/nxos_snmp_location/tests/cli/sanity.yaml b/test/integration/targets/nxos_snmp_location/tests/cli/sanity.yaml
deleted file mode 100644
index 420af74..0000000
--- a/test/integration/targets/nxos_snmp_location/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: "{{ role_path }}/tests/common/sanity.yaml"
diff --git a/test/integration/targets/nxos_snmp_location/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_snmp_location/tests/nxapi/sanity.yaml
deleted file mode 100644
index e30ea6e..0000000
--- a/test/integration/targets/nxos_snmp_location/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: "{{ role_path }}/tests/common/sanity.yaml"
diff --git a/test/integration/targets/nxos_snmp_user/tests/cli/sanity.yaml b/test/integration/targets/nxos_snmp_user/tests/cli/sanity.yaml
deleted file mode 100644
index ddc152e..0000000
--- a/test/integration/targets/nxos_snmp_user/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: targets/nxos_snmp_user/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_snmp_user/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_snmp_user/tests/nxapi/sanity.yaml
deleted file mode 100644
index 1ae7b20..0000000
--- a/test/integration/targets/nxos_snmp_user/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: targets/nxos_snmp_user/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_static_route/defaults/main.yaml b/test/integration/targets/nxos_static_route/defaults/main.yaml
index 5f709c5..525b7aa 100644
--- a/test/integration/targets/nxos_static_route/defaults/main.yaml
+++ b/test/integration/targets/nxos_static_route/defaults/main.yaml
@@ -1,2 +1,5 @@
---
testcase: "*"
+vrfs:
+ - default
+ - myvrf
diff --git a/test/integration/targets/nxos_static_route/tests/cli/sanity.yaml b/test/integration/targets/nxos_static_route/tests/cli/sanity.yaml
deleted file mode 100644
index ad856f1..0000000
--- a/test/integration/targets/nxos_static_route/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: targets/nxos_static_route/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_static_route/tests/common/sanity.yaml b/test/integration/targets/nxos_static_route/tests/common/sanity.yaml
index 0a1a9da..3518cef 100644
--- a/test/integration/targets/nxos_static_route/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_static_route/tests/common/sanity.yaml
@@ -11,8 +11,9 @@
route_name: testing
pref: 100
tag: 5500
- vrf: testing
+ vrf: "{{ item }}"
provider: "{{ connection }}"
+ with_items: "{{ vrfs }}"
register: result
- assert: &true
@@ -21,28 +22,51 @@
- name: "Conf Idempotence"
nxos_static_route: *configure
+ with_items: "{{ vrfs }}"
register: result
- assert: &false
that:
- "result.changed == false"
+ - name: change static route
+ nxos_static_route: &configure1
+ prefix: "192.168.20.64/24"
+ next_hop: "3.3.3.3"
+ route_name: default
+ pref: 10
+ tag: default
+ vrf: "{{ item }}"
+ provider: "{{ connection }}"
+ with_items: "{{ vrfs }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Conf1 Idempotence"
+ nxos_static_route: *configure1
+ with_items: "{{ vrfs }}"
+ register: result
+
+ - assert: *false
+
- name: remove static route
nxos_static_route: &remove
prefix: "192.168.20.64/24"
next_hop: "3.3.3.3"
route_name: testing
pref: 100
- tag: 5500
- vrf: testing
+ vrf: "{{ item }}"
state: absent
provider: "{{ connection }}"
+ with_items: "{{ vrfs }}"
register: result
- assert: *true
- name: "Remove Idempotence"
nxos_static_route: *remove
+ with_items: "{{ vrfs }}"
register: result
- assert: *false
@@ -96,9 +120,10 @@
route_name: testing
pref: 100
tag: 5500
- vrf: testing
+ vrf: "{{ item }}"
state: absent
provider: "{{ connection }}"
+ with_items: "{{ vrfs }}"
ignore_errors: yes
- name: remove static route aggregate
diff --git a/test/integration/targets/nxos_static_route/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_static_route/tests/nxapi/sanity.yaml
deleted file mode 100644
index 5b4e0f8..0000000
--- a/test/integration/targets/nxos_static_route/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: targets/nxos_static_route/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_switchport/tests/common/sanity.yaml b/test/integration/targets/nxos_switchport/tests/common/sanity.yaml
index 20e6e30..899d5ac 100644
--- a/test/integration/targets/nxos_switchport/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_switchport/tests/common/sanity.yaml
@@ -88,7 +88,7 @@
- assert: *false
- - name: Ensure these VLANs are not being tagged on the trunk
+ - name: Remove full trunk vlan range 2-50.
nxos_switchport: &no_tag
interface: "{{ intname }}"
mode: trunk
@@ -99,12 +99,41 @@
- assert: *true
- - name: "no tag vlan Idempotence"
+ - name: Check Idempotence Remove full trunk vlan range 2-50.
nxos_switchport: *no_tag
register: result
- assert: *false
+ - name: Reconfigure interface trunk port and ensure 2-50 are being tagged
+ nxos_switchport: *tag
+ register: result
+
+ - assert: *true
+
+ - name: Check Idempotence Reconfigure interface trunk port and ensure 2-50 are being tagged
+ nxos_switchport: *tag
+ register: result
+
+ - assert: *false
+
+ - name: Remove partial trunk vlan range 30-4094 are removed
+ nxos_switchport: &partial
+ interface: "{{ intname }}"
+ mode: trunk
+ trunk_vlans: 30-4094
+ state: absent
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: Check Idempotence Remove partial trunk vlan range 30-4094 are removed
+ nxos_switchport: *partial
+ register: result
+
+ - assert: *false
+
- name: put interface default state
nxos_switchport: *def_swi
register: result
diff --git a/test/integration/targets/nxos_udld/tests/cli/sanity.yaml b/test/integration/targets/nxos_udld/tests/cli/sanity.yaml
deleted file mode 100644
index 50c7c43..0000000
--- a/test/integration/targets/nxos_udld/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: targets/nxos_udld/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_udld/tests/common/sanity.yaml b/test/integration/targets/nxos_udld/tests/common/sanity.yaml
index 4bf7243..2194476 100644
--- a/test/integration/targets/nxos_udld/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_udld/tests/common/sanity.yaml
@@ -5,7 +5,7 @@
- set_fact: udld_run="true"
- set_fact: udld_run="false"
- when: (( platform is search('N9K-F')) and (imagetag and ( imagetag is version('F3', 'lt'))))
+ when: ((platform is search('N9K-F')) and (imagetag and (imagetag is version_compare('F3', 'lt'))))
- set_fact: udld_run="false"
when: titanium
@@ -16,14 +16,9 @@
state: enabled
provider: "{{ connection }}"
- - name: Reset udld
- nxos_udld:
- reset: True
- provider: "{{ connection }}"
-
- - name: Ensure udld agg mode is globally disabled and msg time is 20
+ - name: Configure udld
nxos_udld: &conf1
- aggressive: disabled
+ aggressive: enabled
msg_time: 20
provider: "{{ connection }}"
register: result
@@ -32,7 +27,7 @@
that:
- "result.changed == true"
- - name: "Conf1 Idempotence"
+ - name: "Check Idempotence"
nxos_udld: *conf1
register: result
@@ -40,22 +35,59 @@
that:
- "result.changed == false"
+ - name: Reset udld
+ nxos_udld:
+ reset: True
+ provider: "{{ connection }}"
- - name: Ensure udld agg mode is globally enabled and msg time is 15
+ - name: Configure udld2
nxos_udld: &conf2
- aggressive: enabled
- msg_time: 15
+ aggressive: disabled
provider: "{{ connection }}"
register: result
- assert: *true
- - name: "conf2 Idempotence"
+ - name: "Check Idempotence"
nxos_udld: *conf2
register: result
- assert: *false
+ - name: Configure udld3
+ nxos_udld: &conf3
+ msg_time: default
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Check Idempotence"
+ nxos_udld: *conf3
+ register: result
+
+ - assert: *false
+
+ - name: Configure udld again
+ nxos_udld: *conf1
+ register: result
+
+ - assert: *true
+
+ - name: Remove udld config
+ nxos_udld: &conf4
+ state: absent
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Check Idempotence"
+ nxos_udld: *conf4
+ register: result
+
+ - assert: *false
+
when: udld_run
always:
diff --git a/test/integration/targets/nxos_udld/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_udld/tests/nxapi/sanity.yaml
deleted file mode 100644
index 2eecada..0000000
--- a/test/integration/targets/nxos_udld/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: targets/nxos_udld/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_udld_interface/tests/cli/sanity.yaml b/test/integration/targets/nxos_udld_interface/tests/cli/sanity.yaml
deleted file mode 100644
index 50cbac8..0000000
--- a/test/integration/targets/nxos_udld_interface/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: targets/nxos_udld_interface/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_udld_interface/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_udld_interface/tests/nxapi/sanity.yaml
deleted file mode 100644
index 5cfe70c..0000000
--- a/test/integration/targets/nxos_udld_interface/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: targets/nxos_udld_interface/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_user/tests/common/sanity.yaml b/test/integration/targets/nxos_user/tests/common/sanity.yaml
new file mode 100644
index 0000000..6fbb084
--- /dev/null
+++ b/test/integration/targets/nxos_user/tests/common/sanity.yaml
@@ -0,0 +1,117 @@
+---
+- debug: msg="START connection={{ ansible_connection }} nxos_user parameter test"
+- debug: msg="Using provider={{ connection.transport }}"
+ when: ansible_connection == "local"
+
+- set_fact: idem="true"
+- set_fact: idem="false"
+ when: ((platform is search('N7K')) and (imagetag and (imagetag is version_compare('D1', 'eq'))))
+
+- block:
+ - name: Create user
+ nxos_user: &configure
+ name: netend
+ configured_password: Hello!23$
+ update_password: on_create
+ roles: network-operator
+ state: present
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: &true
+ that:
+ - 'result.changed == true'
+
+ - block:
+ - name: conf idempotency
+ nxos_user: *configure
+ register: result
+
+ - assert: &false
+ that:
+ - 'result.changed == false'
+ when: idem
+
+ - name: Remove user
+ nxos_user: &remove
+ name: netend
+ state: absent
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: remove idempotency
+ nxos_user: *remove
+ register: result
+
+ - assert: *false
+
+ - debug: msg="skipping sshkey test as the key needs to be created on the server first"
+
+# - name: create a new user
+# nxos_user: &conf1
+# name: ansible
+# sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
+# state: present
+# provider: "{{ connection }}"
+# register: result
+#
+# - assert: *true
+
+# - block:
+# - name: user idempotency
+# nxos_user: *conf1
+# register: result
+#
+# - assert: *false
+#
+# when: idem
+
+ - name: Collection of users
+ nxos_user: &coll
+ users:
+ - name: test1
+ - name: test2
+ configured_password: Hello!23$
+ update_password: on_create
+ state: present
+ roles:
+ - network-admin
+ - network-operator
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - block:
+ - name: users idempotency
+ nxos_user: *coll
+ register: result
+
+ - assert: *false
+
+ when: idem
+
+ - name: tearDown
+ nxos_user: &tear
+ name: ansible
+ purge: yes
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: teardown idempotency
+ nxos_user: *tear
+ register: result
+
+ - assert: *false
+
+ always:
+ - name: tearDown
+ nxos_user: *tear
+ register: result
+ ignore_errors: yes
+
+- debug: msg="END connection={{ ansible_connection }} nxos_user parameter test"
diff --git a/test/integration/targets/nxos_vlan/tests/common/agg.yaml b/test/integration/targets/nxos_vlan/tests/common/agg.yaml
index 3d4fc4b..07d95de 100644
--- a/test/integration/targets/nxos_vlan/tests/common/agg.yaml
+++ b/test/integration/targets/nxos_vlan/tests/common/agg.yaml
@@ -63,6 +63,27 @@
that:
- 'result.changed == false'
+- name: purge
+ nxos_vlan: &purge
+ vlan_id: 1
+ purge: yes
+ provider: "{{ connection }}"
+ register: result
+
+- assert:
+ that:
+ - 'result.changed == true'
+ - '"no vlan 102" in result.commands'
+ - '"no vlan 103" in result.commands'
+
+- name: purge - Idempotence
+ nxos_vlan: *purge
+ register: result
+
+- assert:
+ that:
+ - 'result.changed == false'
+
- name: teardown
nxos_config: *rm
ignore_errors: yes
diff --git a/test/integration/targets/nxos_vlan/tests/common/interface.yaml b/test/integration/targets/nxos_vlan/tests/common/interface.yaml
index cba7372..6e74537 100644
--- a/test/integration/targets/nxos_vlan/tests/common/interface.yaml
+++ b/test/integration/targets/nxos_vlan/tests/common/interface.yaml
@@ -7,6 +7,7 @@
lines:
- no vlan 100
provider: "{{ connection }}"
+ ignore_errors: yes
- name: setup - remove vlan from interfaces used in test(part1)
nxos_config:
diff --git a/test/integration/targets/nxos_vlan/tests/common/sanity.yaml b/test/integration/targets/nxos_vlan/tests/common/sanity.yaml
index 50237ef..4f9428c 100644
--- a/test/integration/targets/nxos_vlan/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_vlan/tests/common/sanity.yaml
@@ -3,6 +3,9 @@
- debug: msg="Using provider={{ connection.transport }}"
when: ansible_connection == "local"
+- set_fact: testint1="{{ nxos_int1 }}"
+- set_fact: testint2="{{ nxos_int2 }}"
+
- block:
- name: "Enable feature vn segment"
nxos_config:
@@ -44,7 +47,7 @@
- assert: *true
when: platform is search('N9K')
- - name: "web Idempotence"
+ - name: "web1 Idempotence"
nxos_vlan: *web1
register: result
when: platform is search('N9K')
@@ -52,9 +55,31 @@
- assert: *false
when: platform is search('N9K')
- - name: Ensure VLAN 50 exists with the name WEB and is in the shutdown state
+ - name: change name and vni to default
nxos_vlan: &web2
vlan_id: 50
+ vlan_state: active
+ admin_state: up
+ name: default
+ mapped_vni: default
+ provider: "{{ connection }}"
+ register: result
+ when: platform is search('N9K')
+
+ - assert: *true
+ when: platform is search('N9K')
+
+ - name: "web2 Idempotence"
+ nxos_vlan: *web2
+ register: result
+ when: platform is search('N9K')
+
+ - assert: *false
+ when: platform is search('N9K')
+
+ - name: Ensure VLAN 50 exists with the name WEB and is in the shutdown state
+ nxos_vlan: &web3
+ vlan_id: 50
vlan_state: suspend
admin_state: down
name: WEB
@@ -65,14 +90,74 @@
- assert: *true
when: platform is search('N3K|N7K')
- - name: "web Idempotence"
- nxos_vlan: *web2
+ - name: "web3 Idempotence"
+ nxos_vlan: *web3
register: result
when: platform is search('N3K|N7K')
- assert: *false
when: platform is search('N3K|N7K')
+ - name: Change name to default
+ nxos_vlan: &web4
+ vlan_id: 50
+ vlan_state: active
+ admin_state: up
+ name: default
+ provider: "{{ connection }}"
+ register: result
+ when: platform is search('N3K|N7K')
+
+ - assert: *true
+ when: platform is search('N3K|N7K')
+
+ - name: "web4 Idempotence"
+ nxos_vlan: *web4
+ register: result
+ when: platform is search('N3K|N7K')
+
+ - assert: *false
+ when: platform is search('N3K|N7K')
+
+# Uncomment this once the get_capabilities() work on nxapi as well
+# - name: Change mode
+# nxos_vlan: &mode1
+# vlan_id: 50
+# mode: fabricpath
+# provider: "{{ connection }}"
+# register: result
+# when: platform is search('N5k|N7K')
+#
+# - assert: *true
+# when: platform is search('N5k|N7K')
+#
+# - name: "mode1 Idempotence"
+# nxos_vlan: *mode1
+# register: result
+# when: platform is search('N5k|N7K')
+#
+# - assert: *false
+# when: platform is search('N5k|N7K')
+#
+# - name: Change mode again
+# nxos_vlan: &mode2
+# vlan_id: 50
+# mode: ce
+# provider: "{{ connection }}"
+# register: result
+# when: platform is search('N5k|N7K')
+#
+# - assert: *true
+# when: platform is search('N5k|N7K')
+#
+# - name: "mode2 Idempotence"
+# nxos_vlan: *mode2
+# register: result
+# when: platform is search('N5k|N7K')
+#
+# - assert: *false
+# when: platform is search('N5k|N7K')
+
- name: Ensure VLAN is NOT on the device
nxos_vlan: &no_vlan
vlan_id: 50
@@ -88,7 +173,44 @@
- assert: *false
+ - name: Add interfaces to vlan
+ nxos_vlan: &addint
+ vlan_id: 101
+ vlan_state: suspend
+ interfaces:
+ - "{{ testint1 }}"
+ - "{{ testint2 }}"
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Addint idempotence"
+ nxos_vlan: *addint
+ register: result
+
+ - assert: *false
+
+ - name: Remove interfaces from vlan
+ nxos_vlan: &remint
+ vlan_id: 101
+ interfaces: default
+ provider: "{{ connection }}"
+ register: result
+
+ - assert: *true
+
+ - name: "Remint idempotence"
+ nxos_vlan: *remint
+ register: result
+
+ - assert: *false
+
always:
+ - name: Remove int from vlan
+ nxos_vlan: *remint
+ ignore_errors: yes
+
- name: remove vlans
nxos_vlan:
vlan_range: "2-10,20,50,55-60,100-150"
diff --git a/test/integration/targets/nxos_vpc/tests/cli/sanity.yaml b/test/integration/targets/nxos_vpc/tests/cli/sanity.yaml
deleted file mode 100644
index c1cb1ac..0000000
--- a/test/integration/targets/nxos_vpc/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: targets/nxos_vpc/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_vpc/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_vpc/tests/nxapi/sanity.yaml
deleted file mode 100644
index d04b6a4..0000000
--- a/test/integration/targets/nxos_vpc/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: targets/nxos_vpc/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_vpc_interface/tests/cli/sanity.yaml b/test/integration/targets/nxos_vpc_interface/tests/cli/sanity.yaml
deleted file mode 100644
index e490d8e..0000000
--- a/test/integration/targets/nxos_vpc_interface/tests/cli/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ cli }}"
-
-- import_tasks: targets/nxos_vpc_interface/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_vpc_interface/tests/nxapi/sanity.yaml b/test/integration/targets/nxos_vpc_interface/tests/nxapi/sanity.yaml
deleted file mode 100644
index dea9331..0000000
--- a/test/integration/targets/nxos_vpc_interface/tests/nxapi/sanity.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-- set_fact: connection="{{ nxapi }}"
-
-- import_tasks: targets/nxos_vpc_interface/tests/common/sanity.yaml
diff --git a/test/integration/targets/nxos_vrf/tests/common/sanity.yaml b/test/integration/targets/nxos_vrf/tests/common/sanity.yaml
index e216947..36a4224 100644
--- a/test/integration/targets/nxos_vrf/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_vrf/tests/common/sanity.yaml
@@ -3,21 +3,39 @@
- debug: msg="Using provider={{ connection.transport }}"
when: ansible